@exercode/problem-utils 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +23 -0
  2. package/dist/cli/checkAllProblems.cjs +1 -1
  3. package/dist/cli/checkAllProblems.js +1 -1
  4. package/dist/cli/exercodeProblem.cjs +8 -2
  5. package/dist/cli/exercodeProblem.cjs.map +1 -1
  6. package/dist/cli/exercodeProblem.js +8 -2
  7. package/dist/cli/exercodeProblem.js.map +1 -1
  8. package/dist/cli/runSingleHarness.js.map +1 -1
  9. package/dist/cli/validateLearningMaterial.cjs +2 -0
  10. package/dist/cli/validateLearningMaterial.cjs.map +1 -0
  11. package/dist/cli/validateLearningMaterial.d.ts +7 -0
  12. package/dist/cli/validateLearningMaterial.js +2 -0
  13. package/dist/cli/validateLearningMaterial.js.map +1 -0
  14. package/dist/helpers/judgeByStaticAnalysis.cjs +3 -3
  15. package/dist/helpers/judgeByStaticAnalysis.js +3 -3
  16. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
  17. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
  18. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
  19. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
  20. package/dist/helpers/runHarnessProcess.cjs.map +1 -1
  21. package/dist/helpers/runHarnessProcess.d.ts +4 -1
  22. package/dist/helpers/runHarnessProcess.js.map +1 -1
  23. package/dist/helpers/spawnWithLimits.cjs +2 -0
  24. package/dist/helpers/spawnWithLimits.cjs.map +1 -0
  25. package/dist/helpers/spawnWithLimits.d.ts +27 -0
  26. package/dist/helpers/spawnWithLimits.js +2 -0
  27. package/dist/helpers/spawnWithLimits.js.map +1 -0
  28. package/dist/helpers/spawnWithTimeout.cjs +4 -0
  29. package/dist/helpers/spawnWithTimeout.cjs.map +1 -0
  30. package/dist/helpers/spawnWithTimeout.d.ts +19 -0
  31. package/dist/helpers/spawnWithTimeout.js +4 -0
  32. package/dist/helpers/spawnWithTimeout.js.map +1 -0
  33. package/dist/helpers/stdioJudgeRules.cjs +2 -0
  34. package/dist/helpers/stdioJudgeRules.cjs.map +1 -0
  35. package/dist/helpers/stdioJudgeRules.d.ts +4 -0
  36. package/dist/helpers/stdioJudgeRules.js +2 -0
  37. package/dist/helpers/stdioJudgeRules.js.map +1 -0
  38. package/dist/helpers/temporaryProblemDirCopy.cjs +1 -1
  39. package/dist/helpers/temporaryProblemDirCopy.js +1 -1
  40. package/dist/index.cjs +1 -1
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.js +1 -1
  43. package/dist/learningMaterial/frontmatter.cjs +2 -0
  44. package/dist/learningMaterial/frontmatter.cjs.map +1 -0
  45. package/dist/learningMaterial/frontmatter.d.ts +8 -0
  46. package/dist/learningMaterial/frontmatter.js +2 -0
  47. package/dist/learningMaterial/frontmatter.js.map +1 -0
  48. package/dist/learningMaterial/fsHelpers.cjs +2 -0
  49. package/dist/learningMaterial/fsHelpers.cjs.map +1 -0
  50. package/dist/learningMaterial/fsHelpers.d.ts +30 -0
  51. package/dist/learningMaterial/fsHelpers.js +2 -0
  52. package/dist/learningMaterial/fsHelpers.js.map +1 -0
  53. package/dist/learningMaterial/schemas.cjs +2 -0
  54. package/dist/learningMaterial/schemas.cjs.map +1 -0
  55. package/dist/learningMaterial/schemas.d.ts +241 -0
  56. package/dist/learningMaterial/schemas.js +2 -0
  57. package/dist/learningMaterial/schemas.js.map +1 -0
  58. package/dist/learningMaterial/validateContest.cjs +2 -0
  59. package/dist/learningMaterial/validateContest.cjs.map +1 -0
  60. package/dist/learningMaterial/validateContest.d.ts +16 -0
  61. package/dist/learningMaterial/validateContest.js +2 -0
  62. package/dist/learningMaterial/validateContest.js.map +1 -0
  63. package/dist/learningMaterial/validateCourse.cjs +2 -0
  64. package/dist/learningMaterial/validateCourse.cjs.map +1 -0
  65. package/dist/learningMaterial/validateCourse.d.ts +11 -0
  66. package/dist/learningMaterial/validateCourse.js +2 -0
  67. package/dist/learningMaterial/validateCourse.js.map +1 -0
  68. package/dist/learningMaterial/validateMaterial.cjs +4 -0
  69. package/dist/learningMaterial/validateMaterial.cjs.map +1 -0
  70. package/dist/learningMaterial/validateMaterial.d.ts +34 -0
  71. package/dist/learningMaterial/validateMaterial.js +4 -0
  72. package/dist/learningMaterial/validateMaterial.js.map +1 -0
  73. package/dist/learningMaterial/validateProblem.cjs +2 -0
  74. package/dist/learningMaterial/validateProblem.cjs.map +1 -0
  75. package/dist/learningMaterial/validateProblem.d.ts +6 -0
  76. package/dist/learningMaterial/validateProblem.js +2 -0
  77. package/dist/learningMaterial/validateProblem.js.map +1 -0
  78. package/dist/learningMaterial/validationResult.cjs +3 -0
  79. package/dist/learningMaterial/validationResult.cjs.map +1 -0
  80. package/dist/learningMaterial/validationResult.d.ts +9 -0
  81. package/dist/learningMaterial/validationResult.js +3 -0
  82. package/dist/learningMaterial/validationResult.js.map +1 -0
  83. package/dist/presets/command.cjs +1 -1
  84. package/dist/presets/command.cjs.map +1 -1
  85. package/dist/presets/command.js +1 -1
  86. package/dist/presets/command.js.map +1 -1
  87. package/dist/presets/evaluation.cjs +2 -2
  88. package/dist/presets/evaluation.js +2 -2
  89. package/dist/presets/guiCommand.cjs +2 -2
  90. package/dist/presets/guiCommand.cjs.map +1 -1
  91. package/dist/presets/guiCommand.js +2 -2
  92. package/dist/presets/guiCommand.js.map +1 -1
  93. package/dist/presets/stdio.cjs +1 -1
  94. package/dist/presets/stdio.cjs.map +1 -1
  95. package/dist/presets/stdio.js +1 -1
  96. package/dist/presets/stdio.js.map +1 -1
  97. package/package.json +2 -1
  98. package/dist/helpers/spawnSyncWithTimeout.cjs +0 -2
  99. package/dist/helpers/spawnSyncWithTimeout.cjs.map +0 -1
  100. package/dist/helpers/spawnSyncWithTimeout.d.ts +0 -5
  101. package/dist/helpers/spawnSyncWithTimeout.js +0 -2
  102. package/dist/helpers/spawnSyncWithTimeout.js.map +0 -1
package/README.md CHANGED
@@ -31,6 +31,29 @@ The all-problem check judges serially by default because time limits are measure
31
31
 
32
32
  A standard stdin/stdout problem must NOT commit a `judge.ts` or `debug.ts` that is identical to the default stdio harness: the absence of `judge.ts` marks the problem as standard, and committed copies would drift from the server's defaults. The CLI rejects such files; a file kept intentionally (e.g. to demonstrate the default harness) can add an explanatory comment to be treated as custom.
33
33
 
34
+ ## Validators
35
+
36
+ The CLI also validates learning-material files without running any program, mirroring the checks the Exercode importer applies:
37
+
38
+ ```bash
39
+ # Validate problem directories (problem.md frontmatter, test_cases, model_answers, templates, judge.ts / debug.ts).
40
+ bun x exercode-problem validate-problem <problemDir>...
41
+ # Validate a course directory (course.yaml, lecture materials with embedded questions, problem references).
42
+ bun x exercode-problem validate-course <courseDir> [--problems-dir <dir>]
43
+ # Validate a contest (*.contest.yaml) file.
44
+ bun x exercode-problem validate-contest <contestYamlPath> [--problems-dir <dir>]
45
+ ```
46
+
47
+ Each target prints `OK` or `NG` followed by its errors and warnings; the command exits 1 when any target has an error. `--problems-dir` points to the directory holding the referenced problems; a course is always searched at any depth, since Exercode links a material only to the problems inside its course, so for a course the option must name a directory inside it. The validators are also exported (`validateProblemDirectory`, `validateCourseDirectory`, `validateContestFile`, `validateMaterialFile`).
48
+
49
+ ## Agent skills
50
+
51
+ The [`skills/`](skills/) directory holds skills for AI coding agents that author and review Exercode learning content: `generate-learning-content` (entry point), `generate-course-materials`, `generate-judge-problems`, `generate-judge-contest`, `review-learning-content`, and `setup-exercode-course-repository`. Agents working in this repository load them through the symlinks under `.claude/skills/`; install them elsewhere with the [skills](https://github.com/vercel-labs/skills) CLI:
52
+
53
+ ```bash
54
+ bun x skills add WillBooster/exercode-problem-utils --agent claude-code --agent codex
55
+ ```
56
+
34
57
  ## Test cases
35
58
 
36
59
  A problem keeps its test cases under `test_cases/`. A test case id is the shared name of the following entries, and each entry is optional:
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/testCaseResult.cjs"),n=require("../helpers/defaultStdioHarness.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../helpers/temporaryProblemDirCopy.cjs"),a=require("../types/decisionCode.cjs"),o=require("../helpers/runHarnessProcess.cjs"),s=require("../helpers/readProblemMarkdownFrontMatter.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:path");u=e.__toESM(u,1);let d=require("node:fs/promises");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(a.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let t=_(e),i=u.default.resolve(t.rootDir),a=e=>(u.default.relative(i,e)||`.`).replaceAll(u.default.sep,`/`),o=await v(i),d=o.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(d.length===0)return console.error(o.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${o.length} problem directories under ${i} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of d){let t=await n.findDefaultStdioHarnessFiles(e);if(t.length>0){f.push(`${a(e)}: ${l.formatDefaultHarnessError(t)}`);continue}let i=await c.findModelAnswerDirs(e);if(i.length===0){f.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await b(u.default.join(e,`judge.ts`))&&(await r.readTestCases(u.default.join(e,`test_cases`))).length===0){let t;try{t=await s.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!s.judgesWithoutTestCases(t)){f.push(`${a(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let o=await c.findFailingModelAnswerDirs(e);p.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...o.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=u.default.resolve(process.argv[1]??``),g=0,y=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,p.length))},async()=>{for(;y<p.length;){let e=p[y++];if(!e)return;let t=`${a(e.problemDir)} ${u.default.relative(e.problemDir,e.answerDir).replaceAll(u.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${d.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return x(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let a,s;try{a=g(e,await o.runHarnessProcess([`run`,t,`judge`,u.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){a=x(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await i.forciblyRemoveDirectory(n)}if(s)return a;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return a===void 0?c:x(`${a}; ${c}`)}function g(e,n){let{stdout:r,stderr:i}=n;if(n.failureReason!==void 0)return x(n.failureReason);if(n.exitCode!==0)return x(`harness exited with ${n.exitCode??`a signal`}${i.trim()?`: ${i.trim()}`:``}`);let o=r.split(/\r?\n/).filter(e=>e.startsWith(t.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of o){let n;try{n=t.testCaseResultSchema.safeParse(JSON.parse(e.slice(t.TEST_CASE_RESULT_PREFIX.length)))}catch{n=void 0}if(!n?.success)return x(`malformed test case result line: ${e}`);s.push(n.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==a.DecisionCode.ACCEPTED);return e?x(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===a.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function v(e){let t=[];return await y(e,t),t.toSorted()}async function y(e,t){let n=await d.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await y(u.default.join(e,r.name),t)}async function b(e){try{return await d.default.stat(e),!0}catch{return!1}}function x(e){return e.length<=f?e:`${e.slice(0,f)}...`}exports.checkAllProblems=m;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/testCaseResult.cjs"),n=require("../helpers/defaultStdioHarness.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../helpers/temporaryProblemDirCopy.cjs"),a=require("../helpers/readProblemMarkdownFrontMatter.cjs"),o=require("../types/decisionCode.cjs"),s=require("../helpers/runHarnessProcess.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:path");u=e.__toESM(u,1);let d=require("node:fs/promises");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(o.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let t=_(e),i=u.default.resolve(t.rootDir),o=e=>(u.default.relative(i,e)||`.`).replaceAll(u.default.sep,`/`),s=await v(i),d=s.filter(e=>{let n=o(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(d.length===0)return console.error(s.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${s.length} problem directories under ${i} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of d){let t=await n.findDefaultStdioHarnessFiles(e);if(t.length>0){f.push(`${o(e)}: ${l.formatDefaultHarnessError(t)}`);continue}let i=await c.findModelAnswerDirs(e);if(i.length===0){f.push(`${o(e)}: no model answers found under model_answers/`);continue}if(!await b(u.default.join(e,`judge.ts`))&&(await r.readTestCases(u.default.join(e,`test_cases`))).length===0){let t;try{t=await a.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${o(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!a.judgesWithoutTestCases(t)){f.push(`${o(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let s=await c.findFailingModelAnswerDirs(e);p.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...s.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=u.default.resolve(process.argv[1]??``),g=0,y=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,p.length))},async()=>{for(;y<p.length;){let e=p[y++];if(!e)return;let t=`${o(e.problemDir)} ${u.default.relative(e.problemDir,e.answerDir).replaceAll(u.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${d.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return x(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let a,o;try{a=g(e,await s.runHarnessProcess([`run`,t,`judge`,u.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){a=x(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{o=await i.forciblyRemoveDirectory(n)}if(o)return a;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return a===void 0?c:x(`${a}; ${c}`)}function g(e,n){let{stdout:r,stderr:i}=n;if(n.failureReason!==void 0)return x(n.failureReason);if(n.exitCode!==0)return x(`harness exited with ${n.exitCode??`a signal`}${i.trim()?`: ${i.trim()}`:``}`);let a=r.split(/\r?\n/).filter(e=>e.startsWith(t.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of a){let n;try{n=t.testCaseResultSchema.safeParse(JSON.parse(e.slice(t.TEST_CASE_RESULT_PREFIX.length)))}catch{n=void 0}if(!n?.success)return x(`malformed test case result line: ${e}`);s.push(n.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==o.DecisionCode.ACCEPTED);return e?x(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===o.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function v(e){let t=[];return await y(e,t),t.toSorted()}async function y(e,t){let n=await d.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await y(u.default.join(e,r.name),t)}async function b(e){try{return await d.default.stat(e),!0}catch{return!1}}function x(e){return e.length<=f?e:`${e.slice(0,f)}...`}exports.checkAllProblems=m;
2
2
  //# sourceMappingURL=checkAllProblems.cjs.map
@@ -1,2 +1,2 @@
1
- import{TEST_CASE_RESULT_PREFIX as e,testCaseResultSchema as t}from"../types/testCaseResult.js";import{findDefaultStdioHarnessFiles as n}from"../helpers/defaultStdioHarness.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{copyProblemDirToTemporaryRoot as i,forciblyRemoveDirectory as a}from"../helpers/temporaryProblemDirCopy.js";import{DecisionCode as o}from"../types/decisionCode.js";import{runHarnessProcess as s}from"../helpers/runHarnessProcess.js";import{judgesWithoutTestCases as c,readProblemMarkdownFrontMatter as l}from"../helpers/readProblemMarkdownFrontMatter.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:path";import h from"node:fs/promises";const g=1e3,_=new Map(Object.entries(o).map(([e,t])=>[t,e]));async function v(e){let t=x(e),i=m.resolve(t.rootDir),a=e=>(m.relative(i,e)||`.`).replaceAll(m.sep,`/`),o=await S(i),s=o.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(s.length===0)return console.error(o.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${o.length} problem directories under ${i} were excluded by --only/--skip.`),1;let h=[],g=[];for(let e of s){let t=await n(e);if(t.length>0){h.push(`${a(e)}: ${p(t)}`);continue}let i=await d(e);if(i.length===0){h.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await w(m.join(e,`judge.ts`))&&(await r(m.join(e,`test_cases`))).length===0){let t;try{t=await l(e)}catch(t){h.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!c(t)){h.push(`${a(e)}: ${f}`);continue}}let o=await u(e);g.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...o.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of h)console.error(`✗ ${e}`);let _=m.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${a(e.problemDir)} ${m.relative(e.problemDir,e.answerDir).replaceAll(m.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(h.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${h.length} failed (${g.length} runs, ${s.length} problems)`),h.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i(e.problemDir))}catch(e){return T(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let o,c;try{o=b(e,await s([`run`,t,`judge`,m.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){o=T(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{c=await a(n)}if(c)return o;let l=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return o===void 0?l:T(`${o}; ${l}`)}function b(n,r){let{stdout:i,stderr:a}=r;if(r.failureReason!==void 0)return T(r.failureReason);if(r.exitCode!==0)return T(`harness exited with ${r.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let s=i.split(/\r?\n/).filter(t=>t.startsWith(e)),c=[];for(let n of s){let r;try{r=t.safeParse(JSON.parse(n.slice(e.length)))}catch{r=void 0}if(!r?.success)return T(`malformed test case result line: ${n}`);c.push(r.data)}if(c.length===0)return`no test case results were printed`;if(n.expectation===`accepted`){let e=c.find(e=>e.decisionCode!==o.ACCEPTED);return e?T(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return c.every(e=>e.decisionCode===o.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function S(e){let t=[];return await C(e,t),t.toSorted()}async function C(e,t){let n=await h.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await C(m.join(e,r.name),t)}async function w(e){try{return await h.stat(e),!0}catch{return!1}}function T(e){return e.length<=g?e:`${e.slice(0,g)}...`}export{v as checkAllProblems};
1
+ import{TEST_CASE_RESULT_PREFIX as e,testCaseResultSchema as t}from"../types/testCaseResult.js";import{findDefaultStdioHarnessFiles as n}from"../helpers/defaultStdioHarness.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{copyProblemDirToTemporaryRoot as i,forciblyRemoveDirectory as a}from"../helpers/temporaryProblemDirCopy.js";import{judgesWithoutTestCases as o,readProblemMarkdownFrontMatter as s}from"../helpers/readProblemMarkdownFrontMatter.js";import{DecisionCode as c}from"../types/decisionCode.js";import{runHarnessProcess as l}from"../helpers/runHarnessProcess.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:path";import h from"node:fs/promises";const g=1e3,_=new Map(Object.entries(c).map(([e,t])=>[t,e]));async function v(e){let t=x(e),i=m.resolve(t.rootDir),a=e=>(m.relative(i,e)||`.`).replaceAll(m.sep,`/`),c=await S(i),l=c.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(l.length===0)return console.error(c.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${c.length} problem directories under ${i} were excluded by --only/--skip.`),1;let h=[],g=[];for(let e of l){let t=await n(e);if(t.length>0){h.push(`${a(e)}: ${p(t)}`);continue}let i=await d(e);if(i.length===0){h.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await w(m.join(e,`judge.ts`))&&(await r(m.join(e,`test_cases`))).length===0){let t;try{t=await s(e)}catch(t){h.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!o(t)){h.push(`${a(e)}: ${f}`);continue}}let c=await u(e);g.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...c.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of h)console.error(`✗ ${e}`);let _=m.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${a(e.problemDir)} ${m.relative(e.problemDir,e.answerDir).replaceAll(m.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(h.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${h.length} failed (${g.length} runs, ${l.length} problems)`),h.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i(e.problemDir))}catch(e){return T(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let o,s;try{o=b(e,await l([`run`,t,`judge`,m.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){o=T(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await a(n)}if(s)return o;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return o===void 0?c:T(`${o}; ${c}`)}function b(n,r){let{stdout:i,stderr:a}=r;if(r.failureReason!==void 0)return T(r.failureReason);if(r.exitCode!==0)return T(`harness exited with ${r.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let o=i.split(/\r?\n/).filter(t=>t.startsWith(e)),s=[];for(let n of o){let r;try{r=t.safeParse(JSON.parse(n.slice(e.length)))}catch{r=void 0}if(!r?.success)return T(`malformed test case result line: ${n}`);s.push(r.data)}if(s.length===0)return`no test case results were printed`;if(n.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==c.ACCEPTED);return e?T(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===c.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function S(e){let t=[];return await C(e,t),t.toSorted()}async function C(e,t){let n=await h.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await C(m.join(e,r.name),t)}async function w(e){try{return await h.stat(e),!0}catch{return!1}}function T(e){return e.length<=g?e:`${e.slice(0,g)}...`}export{v as checkAllProblems};
2
2
  //# sourceMappingURL=checkAllProblems.js.map
@@ -1,9 +1,15 @@
1
- "use strict";const e=require("./runSingleHarness.cjs"),t=require("./checkAllProblems.cjs"),n=`Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]
1
+ "use strict";const e=require("./runSingleHarness.cjs"),t=require("./checkAllProblems.cjs"),n=require("./validateLearningMaterial.cjs"),r=`Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]
2
2
  exercode-problem judge <answerDir> [paramsJson]
3
3
  exercode-problem debug <answerDir> [paramsJson]
4
+ exercode-problem validate-problem <problemDir>...
5
+ exercode-problem validate-course <courseDir> [--problems-dir <dir>]
6
+ exercode-problem validate-contest <contestYamlPath> [--problems-dir <dir>]
4
7
 
5
8
  Without a subcommand, judge all model answers of all problems under rootDir
6
9
  (default: the current directory). Use \`judge\` or \`debug\` in a problem
7
10
  directory to run one answer directory, e.g.:
8
- exercode-problem debug model_answers/python '{ "stdin": "1 2" }'`;r();async function r(){let r=process.argv.slice(2);if(r[0]===`--help`||r[0]===`-h`){console.info(n);return}try{if(r[0]===`judge`||r[0]===`debug`){if(r.length<2){console.error(n),process.exitCode=1;return}process.argv.splice(2,1),process.exitCode=await e.runSingleHarness(r[0])}else process.exitCode=await t.checkAllProblems(r)}catch(e){console.error(e),process.exitCode=1}}
11
+ exercode-problem debug model_answers/python '{ "stdin": "1 2" }'
12
+ The \`validate-*\` subcommands check problem directories, a course directory
13
+ (course.yaml and lecture materials), or a contest (*.contest.yaml) file
14
+ without running any program.`;i();async function i(){let i=process.argv.slice(2);if(i[0]===`--help`||i[0]===`-h`){console.info(r);return}try{if(i[0]===`judge`||i[0]===`debug`){if(i.length<2){console.error(r),process.exitCode=1;return}process.argv.splice(2,1),process.exitCode=await e.runSingleHarness(i[0])}else a(i[0])?process.exitCode=await n.validateLearningMaterial(i[0],i.slice(1)):process.exitCode=await t.checkAllProblems(i)}catch(e){console.error(e),process.exitCode=1}}function a(e){return n.VALIDATE_SUBCOMMANDS.includes(e??``)}
9
15
  //# sourceMappingURL=exercodeProblem.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exercodeProblem.cjs","names":["runSingleHarness","checkAllProblems"],"sources":["../../src/cli/exercodeProblem.ts"],"sourcesContent":["import { checkAllProblems } from './checkAllProblems.js';\nimport { runSingleHarness } from './runSingleHarness.js';\n\nconst usage = `Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]\n exercode-problem judge <answerDir> [paramsJson]\n exercode-problem debug <answerDir> [paramsJson]\n\nWithout a subcommand, judge all model answers of all problems under rootDir\n(default: the current directory). Use \\`judge\\` or \\`debug\\` in a problem\ndirectory to run one answer directory, e.g.:\n exercode-problem debug model_answers/python '{ \"stdin\": \"1 2\" }'`;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- the CJS build output does not support top-level await\nvoid main();\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n if (args[0] === '--help' || args[0] === '-h') {\n // console.log would be stripped from the built CLI, so print the usage with console.info.\n console.info(usage);\n return;\n }\n try {\n if (args[0] === 'judge' || args[0] === 'debug') {\n if (args.length < 2) {\n console.error(usage);\n process.exitCode = 1;\n return;\n }\n // Harness scripts and presets read the answer directory and params from fixed positions in\n // `process.argv`, so drop the subcommand to restore those positions.\n process.argv.splice(2, 1);\n process.exitCode = await runSingleHarness(args[0]);\n } else {\n process.exitCode = await checkAllProblems(args);\n }\n } catch (error) {\n // Print the whole error: harness failures need their stack and `cause`, not just the message.\n console.error(error);\n process.exitCode = 1;\n }\n}\n"],"mappings":"2FAGM,EAAQ;;;;;;;oEAUT,EAAK,EAEV,eAAe,GAAsB,CACnC,IAAM,EAAO,QAAQ,KAAK,MAAM,CAAC,EACjC,GAAI,EAAK,KAAO,UAAY,EAAK,KAAO,KAAM,CAE5C,QAAQ,KAAK,CAAK,EAClB,MACF,CACA,GAAI,CACF,GAAI,EAAK,KAAO,SAAW,EAAK,KAAO,QAAS,CAC9C,GAAI,EAAK,OAAS,EAAG,CACnB,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,EACnB,MACF,CAGA,QAAQ,KAAK,OAAO,EAAG,CAAC,EACxB,QAAQ,SAAW,MAAMA,EAAAA,iBAAiB,EAAK,EAAE,CACnD,KACE,SAAQ,SAAW,MAAMC,EAAAA,iBAAiB,CAAI,CAElD,OAAS,EAAO,CAEd,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,CACrB,CACF"}
1
+ {"version":3,"file":"exercodeProblem.cjs","names":["runSingleHarness","validateLearningMaterial","checkAllProblems","VALIDATE_SUBCOMMANDS"],"sources":["../../src/cli/exercodeProblem.ts"],"sourcesContent":["import { checkAllProblems } from './checkAllProblems.js';\nimport { runSingleHarness } from './runSingleHarness.js';\nimport { VALIDATE_SUBCOMMANDS, type ValidateSubcommand, validateLearningMaterial } from './validateLearningMaterial.js';\n\nconst usage = `Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]\n exercode-problem judge <answerDir> [paramsJson]\n exercode-problem debug <answerDir> [paramsJson]\n exercode-problem validate-problem <problemDir>...\n exercode-problem validate-course <courseDir> [--problems-dir <dir>]\n exercode-problem validate-contest <contestYamlPath> [--problems-dir <dir>]\n\nWithout a subcommand, judge all model answers of all problems under rootDir\n(default: the current directory). Use \\`judge\\` or \\`debug\\` in a problem\ndirectory to run one answer directory, e.g.:\n exercode-problem debug model_answers/python '{ \"stdin\": \"1 2\" }'\nThe \\`validate-*\\` subcommands check problem directories, a course directory\n(course.yaml and lecture materials), or a contest (*.contest.yaml) file\nwithout running any program.`;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- the CJS build output does not support top-level await\nvoid main();\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n if (args[0] === '--help' || args[0] === '-h') {\n // console.log would be stripped from the built CLI, so print the usage with console.info.\n console.info(usage);\n return;\n }\n try {\n if (args[0] === 'judge' || args[0] === 'debug') {\n if (args.length < 2) {\n console.error(usage);\n process.exitCode = 1;\n return;\n }\n // Harness scripts and presets read the answer directory and params from fixed positions in\n // `process.argv`, so drop the subcommand to restore those positions.\n process.argv.splice(2, 1);\n process.exitCode = await runSingleHarness(args[0]);\n } else if (isValidateSubcommand(args[0])) {\n process.exitCode = await validateLearningMaterial(args[0], args.slice(1));\n } else {\n process.exitCode = await checkAllProblems(args);\n }\n } catch (error) {\n // Print the whole error: harness failures need their stack and `cause`, not just the message.\n console.error(error);\n process.exitCode = 1;\n }\n}\n\nfunction isValidateSubcommand(arg: string | undefined): arg is ValidateSubcommand {\n return (VALIDATE_SUBCOMMANDS as readonly string[]).includes(arg ?? '');\n}\n"],"mappings":"uIAIM,EAAQ;;;;;;;;;;;;;8BAgBT,EAAK,EAEV,eAAe,GAAsB,CACnC,IAAM,EAAO,QAAQ,KAAK,MAAM,CAAC,EACjC,GAAI,EAAK,KAAO,UAAY,EAAK,KAAO,KAAM,CAE5C,QAAQ,KAAK,CAAK,EAClB,MACF,CACA,GAAI,CACF,GAAI,EAAK,KAAO,SAAW,EAAK,KAAO,QAAS,CAC9C,GAAI,EAAK,OAAS,EAAG,CACnB,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,EACnB,MACF,CAGA,QAAQ,KAAK,OAAO,EAAG,CAAC,EACxB,QAAQ,SAAW,MAAMA,EAAAA,iBAAiB,EAAK,EAAE,CACnD,MAAW,EAAqB,EAAK,EAAE,EACrC,QAAQ,SAAW,MAAMC,EAAAA,yBAAyB,EAAK,GAAI,EAAK,MAAM,CAAC,CAAC,EAExE,QAAQ,SAAW,MAAMC,EAAAA,iBAAiB,CAAI,CAElD,OAAS,EAAO,CAEd,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,CACrB,CACF,CAEA,SAAS,EAAqB,EAAoD,CAChF,OAAQC,EAAAA,qBAA2C,SAAS,GAAO,EAAE,CACvE"}
@@ -1,9 +1,15 @@
1
- import{runSingleHarness as e}from"./runSingleHarness.js";import{checkAllProblems as t}from"./checkAllProblems.js";const n=`Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]
1
+ import{runSingleHarness as e}from"./runSingleHarness.js";import{checkAllProblems as t}from"./checkAllProblems.js";import{VALIDATE_SUBCOMMANDS as n,validateLearningMaterial as r}from"./validateLearningMaterial.js";const i=`Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]
2
2
  exercode-problem judge <answerDir> [paramsJson]
3
3
  exercode-problem debug <answerDir> [paramsJson]
4
+ exercode-problem validate-problem <problemDir>...
5
+ exercode-problem validate-course <courseDir> [--problems-dir <dir>]
6
+ exercode-problem validate-contest <contestYamlPath> [--problems-dir <dir>]
4
7
 
5
8
  Without a subcommand, judge all model answers of all problems under rootDir
6
9
  (default: the current directory). Use \`judge\` or \`debug\` in a problem
7
10
  directory to run one answer directory, e.g.:
8
- exercode-problem debug model_answers/python '{ "stdin": "1 2" }'`;r();async function r(){let r=process.argv.slice(2);if(r[0]===`--help`||r[0]===`-h`){console.info(n);return}try{if(r[0]===`judge`||r[0]===`debug`){if(r.length<2){console.error(n),process.exitCode=1;return}process.argv.splice(2,1),process.exitCode=await e(r[0])}else process.exitCode=await t(r)}catch(e){console.error(e),process.exitCode=1}}
11
+ exercode-problem debug model_answers/python '{ "stdin": "1 2" }'
12
+ The \`validate-*\` subcommands check problem directories, a course directory
13
+ (course.yaml and lecture materials), or a contest (*.contest.yaml) file
14
+ without running any program.`;a();async function a(){let n=process.argv.slice(2);if(n[0]===`--help`||n[0]===`-h`){console.info(i);return}try{if(n[0]===`judge`||n[0]===`debug`){if(n.length<2){console.error(i),process.exitCode=1;return}process.argv.splice(2,1),process.exitCode=await e(n[0])}else o(n[0])?process.exitCode=await r(n[0],n.slice(1)):process.exitCode=await t(n)}catch(e){console.error(e),process.exitCode=1}}function o(e){return n.includes(e??``)}
9
15
  //# sourceMappingURL=exercodeProblem.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exercodeProblem.js","names":[],"sources":["../../src/cli/exercodeProblem.ts"],"sourcesContent":["import { checkAllProblems } from './checkAllProblems.js';\nimport { runSingleHarness } from './runSingleHarness.js';\n\nconst usage = `Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]\n exercode-problem judge <answerDir> [paramsJson]\n exercode-problem debug <answerDir> [paramsJson]\n\nWithout a subcommand, judge all model answers of all problems under rootDir\n(default: the current directory). Use \\`judge\\` or \\`debug\\` in a problem\ndirectory to run one answer directory, e.g.:\n exercode-problem debug model_answers/python '{ \"stdin\": \"1 2\" }'`;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- the CJS build output does not support top-level await\nvoid main();\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n if (args[0] === '--help' || args[0] === '-h') {\n // console.log would be stripped from the built CLI, so print the usage with console.info.\n console.info(usage);\n return;\n }\n try {\n if (args[0] === 'judge' || args[0] === 'debug') {\n if (args.length < 2) {\n console.error(usage);\n process.exitCode = 1;\n return;\n }\n // Harness scripts and presets read the answer directory and params from fixed positions in\n // `process.argv`, so drop the subcommand to restore those positions.\n process.argv.splice(2, 1);\n process.exitCode = await runSingleHarness(args[0]);\n } else {\n process.exitCode = await checkAllProblems(args);\n }\n } catch (error) {\n // Print the whole error: harness failures need their stack and `cause`, not just the message.\n console.error(error);\n process.exitCode = 1;\n }\n}\n"],"mappings":"kHAGA,MAAM,EAAQ;;;;;;;oEAUT,EAAK,EAEV,eAAe,GAAsB,CACnC,IAAM,EAAO,QAAQ,KAAK,MAAM,CAAC,EACjC,GAAI,EAAK,KAAO,UAAY,EAAK,KAAO,KAAM,CAE5C,QAAQ,KAAK,CAAK,EAClB,MACF,CACA,GAAI,CACF,GAAI,EAAK,KAAO,SAAW,EAAK,KAAO,QAAS,CAC9C,GAAI,EAAK,OAAS,EAAG,CACnB,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,EACnB,MACF,CAGA,QAAQ,KAAK,OAAO,EAAG,CAAC,EACxB,QAAQ,SAAW,MAAM,EAAiB,EAAK,EAAE,CACnD,KACE,SAAQ,SAAW,MAAM,EAAiB,CAAI,CAElD,OAAS,EAAO,CAEd,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,CACrB,CACF"}
1
+ {"version":3,"file":"exercodeProblem.js","names":[],"sources":["../../src/cli/exercodeProblem.ts"],"sourcesContent":["import { checkAllProblems } from './checkAllProblems.js';\nimport { runSingleHarness } from './runSingleHarness.js';\nimport { VALIDATE_SUBCOMMANDS, type ValidateSubcommand, validateLearningMaterial } from './validateLearningMaterial.js';\n\nconst usage = `Usage: exercode-problem [rootDir] [--only <substring>]... [--skip <substring>]... [--concurrency <n>]\n exercode-problem judge <answerDir> [paramsJson]\n exercode-problem debug <answerDir> [paramsJson]\n exercode-problem validate-problem <problemDir>...\n exercode-problem validate-course <courseDir> [--problems-dir <dir>]\n exercode-problem validate-contest <contestYamlPath> [--problems-dir <dir>]\n\nWithout a subcommand, judge all model answers of all problems under rootDir\n(default: the current directory). Use \\`judge\\` or \\`debug\\` in a problem\ndirectory to run one answer directory, e.g.:\n exercode-problem debug model_answers/python '{ \"stdin\": \"1 2\" }'\nThe \\`validate-*\\` subcommands check problem directories, a course directory\n(course.yaml and lecture materials), or a contest (*.contest.yaml) file\nwithout running any program.`;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- the CJS build output does not support top-level await\nvoid main();\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n if (args[0] === '--help' || args[0] === '-h') {\n // console.log would be stripped from the built CLI, so print the usage with console.info.\n console.info(usage);\n return;\n }\n try {\n if (args[0] === 'judge' || args[0] === 'debug') {\n if (args.length < 2) {\n console.error(usage);\n process.exitCode = 1;\n return;\n }\n // Harness scripts and presets read the answer directory and params from fixed positions in\n // `process.argv`, so drop the subcommand to restore those positions.\n process.argv.splice(2, 1);\n process.exitCode = await runSingleHarness(args[0]);\n } else if (isValidateSubcommand(args[0])) {\n process.exitCode = await validateLearningMaterial(args[0], args.slice(1));\n } else {\n process.exitCode = await checkAllProblems(args);\n }\n } catch (error) {\n // Print the whole error: harness failures need their stack and `cause`, not just the message.\n console.error(error);\n process.exitCode = 1;\n }\n}\n\nfunction isValidateSubcommand(arg: string | undefined): arg is ValidateSubcommand {\n return (VALIDATE_SUBCOMMANDS as readonly string[]).includes(arg ?? '');\n}\n"],"mappings":"qNAIA,MAAM,EAAQ;;;;;;;;;;;;;8BAgBT,EAAK,EAEV,eAAe,GAAsB,CACnC,IAAM,EAAO,QAAQ,KAAK,MAAM,CAAC,EACjC,GAAI,EAAK,KAAO,UAAY,EAAK,KAAO,KAAM,CAE5C,QAAQ,KAAK,CAAK,EAClB,MACF,CACA,GAAI,CACF,GAAI,EAAK,KAAO,SAAW,EAAK,KAAO,QAAS,CAC9C,GAAI,EAAK,OAAS,EAAG,CACnB,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,EACnB,MACF,CAGA,QAAQ,KAAK,OAAO,EAAG,CAAC,EACxB,QAAQ,SAAW,MAAM,EAAiB,EAAK,EAAE,CACnD,MAAW,EAAqB,EAAK,EAAE,EACrC,QAAQ,SAAW,MAAM,EAAyB,EAAK,GAAI,EAAK,MAAM,CAAC,CAAC,EAExE,QAAQ,SAAW,MAAM,EAAiB,CAAI,CAElD,OAAS,EAAO,CAEd,QAAQ,MAAM,CAAK,EACnB,QAAQ,SAAW,CACrB,CACF,CAEA,SAAS,EAAqB,EAAoD,CAChF,OAAQ,EAA2C,SAAS,GAAO,EAAE,CACvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"runSingleHarness.js","names":[],"sources":["../../src/cli/runSingleHarness.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { stdioDebugPreset, stdioJudgePreset } from '../presets/stdio.js';\n\nexport const MISSING_TEST_CASES_ERROR =\n 'a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired';\n\n/**\n * Run the judge or debug harness of the problem in the current working directory: a custom\n * `judge.ts` / `debug.ts` when present, or the default stdio preset otherwise (mirroring the\n * Exercode server, which applies the stdio presets to problems without these files).\n */\nexport async function runSingleHarness(kind: 'judge' | 'debug'): Promise<number> {\n const problemDir = process.cwd();\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n console.error(formatDefaultHarnessError(defaultHarnessFileNames));\n return 1;\n }\n\n const harnessFileName = kind === 'judge' ? 'judge.ts' : 'debug.ts';\n if (fs.existsSync(path.join(problemDir, harnessFileName))) {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const spawnResult = child_process.spawnSync(process.execPath, ['run', harnessFileName, ...process.argv.slice(2)], {\n cwd: problemDir,\n stdio: 'inherit',\n });\n if (spawnResult.error) {\n console.error(`failed to run ${harnessFileName}: ${spawnResult.error.message}`);\n return 1;\n }\n return spawnResult.status ?? 1;\n }\n\n // A custom judge.ts marks the problem as non-standard, so the stdio debug preset would judge the\n // answer against a contract the problem does not have (mirroring the server, which reports debug\n // as unsupported in this case).\n if (kind === 'debug' && fs.existsSync(path.join(problemDir, 'judge.ts'))) {\n console.error('This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.');\n return 1;\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 (kind === 'judge') {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0 && !judgesWithoutTestCases(await readProblemMarkdownFrontMatter(problemDir))) {\n console.error(MISSING_TEST_CASES_ERROR);\n return 1;\n }\n }\n\n // The presets read the answer directory and params from `process.argv`, which holds them at the\n // same positions as when a harness script is invoked directly.\n await (kind === 'judge' ? stdioJudgePreset(problemDir) : stdioDebugPreset(problemDir));\n return 0;\n}\n\nexport function formatDefaultHarnessError(defaultHarnessFileNames: readonly string[]): string {\n const fileNames = defaultHarnessFileNames.join(' and ');\n return `${fileNames} must not be committed because the content is identical to the default stdio harness.\nA problem without judge.ts is automatically judged with stdioJudgePreset and debugged with stdioDebugPreset,\nso delete ${fileNames} and run the problem with \\`bun x exercode-problem judge <answerDir>\\` or \\`bun x exercode-problem debug <answerDir>\\`.\nIf the file intentionally demonstrates the default harness, add an explanatory comment to mark it as custom.`;\n}\n"],"mappings":"saASA,MAAa,EACX,6KAOF,eAAsB,EAAiB,EAA0C,CAC/E,IAAM,EAAa,QAAQ,IAAI,EAEzB,EAA0B,MAAM,EAA6B,CAAU,EAC7E,GAAI,EAAwB,OAAS,EAEnC,OADA,QAAQ,MAAM,EAA0B,CAAuB,CAAC,EACzD,EAGT,IAAM,EAAkB,IAAS,QAAU,WAAa,WACxD,GAAI,EAAG,WAAW,EAAK,KAAK,EAAY,CAAe,CAAC,EAAG,CAEzD,IAAM,EAAc,EAAc,UAAU,QAAQ,SAAU,CAAC,MAAO,EAAiB,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAG,CAChH,IAAK,EACL,MAAO,SACT,CAAC,EAKD,OAJI,EAAY,OACd,QAAQ,MAAM,iBAAiB,EAAgB,IAAI,EAAY,MAAM,SAAS,EACvE,GAEF,EAAY,QAAU,CAC/B,CAwBA,OAnBI,IAAS,SAAW,EAAG,WAAW,EAAK,KAAK,EAAY,UAAU,CAAC,GACrE,QAAQ,MAAM,0FAA0F,EACjG,GAML,IAAS,UAEP,MADoB,EAAc,EAAK,KAAK,EAAY,YAAY,CAAC,EAAA,CAC3D,SAAW,GAAK,CAAC,EAAuB,MAAM,EAA+B,CAAU,CAAC,GACpG,QAAQ,MAAM,CAAwB,EAC/B,IAMX,MAAO,IAAS,QAAU,EAAiB,CAAU,EAAI,EAAiB,CAAU,GAC7E,EACT,CAEA,SAAgB,EAA0B,EAAoD,CAC5F,IAAM,EAAY,EAAwB,KAAK,OAAO,EACtD,MAAO,GAAG,EAAU;;YAEV,EAAU;6GAEtB"}
1
+ {"version":3,"file":"runSingleHarness.js","names":["child_process"],"sources":["../../src/cli/runSingleHarness.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { stdioDebugPreset, stdioJudgePreset } from '../presets/stdio.js';\n\nexport const MISSING_TEST_CASES_ERROR =\n 'a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired';\n\n/**\n * Run the judge or debug harness of the problem in the current working directory: a custom\n * `judge.ts` / `debug.ts` when present, or the default stdio preset otherwise (mirroring the\n * Exercode server, which applies the stdio presets to problems without these files).\n */\nexport async function runSingleHarness(kind: 'judge' | 'debug'): Promise<number> {\n const problemDir = process.cwd();\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n console.error(formatDefaultHarnessError(defaultHarnessFileNames));\n return 1;\n }\n\n const harnessFileName = kind === 'judge' ? 'judge.ts' : 'debug.ts';\n if (fs.existsSync(path.join(problemDir, harnessFileName))) {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const spawnResult = child_process.spawnSync(process.execPath, ['run', harnessFileName, ...process.argv.slice(2)], {\n cwd: problemDir,\n stdio: 'inherit',\n });\n if (spawnResult.error) {\n console.error(`failed to run ${harnessFileName}: ${spawnResult.error.message}`);\n return 1;\n }\n return spawnResult.status ?? 1;\n }\n\n // A custom judge.ts marks the problem as non-standard, so the stdio debug preset would judge the\n // answer against a contract the problem does not have (mirroring the server, which reports debug\n // as unsupported in this case).\n if (kind === 'debug' && fs.existsSync(path.join(problemDir, 'judge.ts'))) {\n console.error('This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.');\n return 1;\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 (kind === 'judge') {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0 && !judgesWithoutTestCases(await readProblemMarkdownFrontMatter(problemDir))) {\n console.error(MISSING_TEST_CASES_ERROR);\n return 1;\n }\n }\n\n // The presets read the answer directory and params from `process.argv`, which holds them at the\n // same positions as when a harness script is invoked directly.\n await (kind === 'judge' ? stdioJudgePreset(problemDir) : stdioDebugPreset(problemDir));\n return 0;\n}\n\nexport function formatDefaultHarnessError(defaultHarnessFileNames: readonly string[]): string {\n const fileNames = defaultHarnessFileNames.join(' and ');\n return `${fileNames} must not be committed because the content is identical to the default stdio harness.\nA problem without judge.ts is automatically judged with stdioJudgePreset and debugged with stdioDebugPreset,\nso delete ${fileNames} and run the problem with \\`bun x exercode-problem judge <answerDir>\\` or \\`bun x exercode-problem debug <answerDir>\\`.\nIf the file intentionally demonstrates the default harness, add an explanatory comment to mark it as custom.`;\n}\n"],"mappings":"saASA,MAAa,EACX,6KAOF,eAAsB,EAAiB,EAA0C,CAC/E,IAAM,EAAa,QAAQ,IAAI,EAEzB,EAA0B,MAAM,EAA6B,CAAU,EAC7E,GAAI,EAAwB,OAAS,EAEnC,OADA,QAAQ,MAAM,EAA0B,CAAuB,CAAC,EACzD,EAGT,IAAM,EAAkB,IAAS,QAAU,WAAa,WACxD,GAAI,EAAG,WAAW,EAAK,KAAK,EAAY,CAAe,CAAC,EAAG,CAEzD,IAAM,EAAcA,EAAc,UAAU,QAAQ,SAAU,CAAC,MAAO,EAAiB,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAG,CAChH,IAAK,EACL,MAAO,SACT,CAAC,EAKD,OAJI,EAAY,OACd,QAAQ,MAAM,iBAAiB,EAAgB,IAAI,EAAY,MAAM,SAAS,EACvE,GAEF,EAAY,QAAU,CAC/B,CAwBA,OAnBI,IAAS,SAAW,EAAG,WAAW,EAAK,KAAK,EAAY,UAAU,CAAC,GACrE,QAAQ,MAAM,0FAA0F,EACjG,GAML,IAAS,UAEP,MADoB,EAAc,EAAK,KAAK,EAAY,YAAY,CAAC,EAAA,CAC3D,SAAW,GAAK,CAAC,EAAuB,MAAM,EAA+B,CAAU,CAAC,GACpG,QAAQ,MAAM,CAAwB,EAC/B,IAMX,MAAO,IAAS,QAAU,EAAiB,CAAU,EAAI,EAAiB,CAAU,GAC7E,EACT,CAEA,SAAgB,EAA0B,EAAoD,CAC5F,IAAM,EAAY,EAAwB,KAAK,OAAO,EACtD,MAAO,GAAG,EAAU;;YAEV,EAAU;6GAEtB"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../learningMaterial/validationResult.cjs"),t=require("../learningMaterial/validateContest.cjs"),n=require("../learningMaterial/validateCourse.cjs"),r=require("../learningMaterial/validateProblem.cjs"),i=[`validate-problem`,`validate-course`,`validate-contest`];async function a(t,n){let{targets:r,problemsDir:i}=s(n);if(r.length===0)throw Error(`${t} requires at least one path`);if(t===`validate-problem`){if(i!==void 0)throw Error(`${t} does not take --problems-dir`)}else if(r.length>1)throw Error(`${t} accepts exactly one path, but got ${r.length}`);let a=!1;for(let n of r){let r=await o(t,n,i);process.stdout.write(e.formatValidationReport(n,r)),a||=r.errors.length>0}return+!!a}async function o(e,i,a){switch(e){case`validate-problem`:return r.validateProblemDirectory(i);case`validate-course`:return n.validateCourseDirectory(i,{problemsDirectoryPath:a});case`validate-contest`:return t.validateContestFile(i,{problemsDirectoryPath:a})}}function s(e){let t=[],n;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--problems-dir`){if(n=e[++r],n===void 0)throw Error(`${i} requires a value`)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else t.push(i)}return{targets:t,problemsDir:n}}exports.VALIDATE_SUBCOMMANDS=i,exports.validateLearningMaterial=a;
2
+ //# sourceMappingURL=validateLearningMaterial.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateLearningMaterial.cjs","names":["formatValidationReport","validateProblemDirectory","validateCourseDirectory","validateContestFile"],"sources":["../../src/cli/validateLearningMaterial.ts"],"sourcesContent":["import { validateContestFile } from '../learningMaterial/validateContest.js';\nimport { validateCourseDirectory } from '../learningMaterial/validateCourse.js';\nimport { validateProblemDirectory } from '../learningMaterial/validateProblem.js';\nimport { formatValidationReport, type ValidationResult } from '../learningMaterial/validationResult.js';\n\nexport const VALIDATE_SUBCOMMANDS = ['validate-problem', 'validate-course', 'validate-contest'] as const;\n\nexport type ValidateSubcommand = (typeof VALIDATE_SUBCOMMANDS)[number];\n\n/**\n * Validate learning-material files without running any program and print one report per target.\n * Returns the process exit code: 1 when any target has an error (warnings alone pass).\n */\nexport async function validateLearningMaterial(\n subcommand: ValidateSubcommand,\n args: readonly string[]\n): Promise<number> {\n const { targets, problemsDir } = parseValidateArgs(args);\n if (targets.length === 0) throw new Error(`${subcommand} requires at least one path`);\n if (subcommand === 'validate-problem') {\n if (problemsDir !== undefined) throw new Error(`${subcommand} does not take --problems-dir`);\n } else if (targets.length > 1) {\n throw new Error(`${subcommand} accepts exactly one path, but got ${targets.length}`);\n }\n\n let hasError = false;\n for (const target of targets) {\n const result = await validate(subcommand, target, problemsDir);\n process.stdout.write(formatValidationReport(target, result));\n hasError ||= result.errors.length > 0;\n }\n return hasError ? 1 : 0;\n}\n\nasync function validate(\n subcommand: ValidateSubcommand,\n target: string,\n problemsDirectoryPath: string | undefined\n): Promise<ValidationResult> {\n switch (subcommand) {\n case 'validate-problem': {\n return validateProblemDirectory(target);\n }\n case 'validate-course': {\n return validateCourseDirectory(target, { problemsDirectoryPath });\n }\n case 'validate-contest': {\n return validateContestFile(target, { problemsDirectoryPath });\n }\n }\n}\n\nfunction parseValidateArgs(args: readonly string[]): { targets: string[]; problemsDir: string | undefined } {\n const targets: string[] = [];\n let problemsDir: string | undefined;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--problems-dir') {\n problemsDir = args[++index];\n if (problemsDir === undefined) throw new Error(`${arg} requires a value`);\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else {\n targets.push(arg);\n }\n }\n return { targets, problemsDir };\n}\n"],"mappings":"0SAKa,EAAuB,CAAC,mBAAoB,kBAAmB,kBAAkB,EAQ9F,eAAsB,EACpB,EACA,EACiB,CACjB,GAAM,CAAE,UAAS,eAAgB,EAAkB,CAAI,EACvD,GAAI,EAAQ,SAAW,EAAG,MAAU,MAAM,GAAG,EAAW,4BAA4B,EACpF,GAAI,IAAe,mBACb,IAAA,IAAgB,IAAA,GAAW,MAAU,MAAM,GAAG,EAAW,8BAA8B,CAAA,MACtF,GAAI,EAAQ,OAAS,EAC1B,MAAU,MAAM,GAAG,EAAW,qCAAqC,EAAQ,QAAQ,EAGrF,IAAI,EAAW,GACf,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAS,MAAM,EAAS,EAAY,EAAQ,CAAW,EAC7D,QAAQ,OAAO,MAAMA,EAAAA,uBAAuB,EAAQ,CAAM,CAAC,EAC3D,IAAa,EAAO,OAAO,OAAS,CACtC,CACA,MAAO,IACT,CAEA,eAAe,EACb,EACA,EACA,EAC2B,CAC3B,OAAQ,EAAR,CACE,IAAK,mBACH,OAAOC,EAAAA,yBAAyB,CAAM,EAExC,IAAK,kBACH,OAAOC,EAAAA,wBAAwB,EAAQ,CAAE,uBAAsB,CAAC,EAElE,IAAK,mBACH,OAAOC,EAAAA,oBAAoB,EAAQ,CAAE,uBAAsB,CAAC,CAEhE,CACF,CAEA,SAAS,EAAkB,EAAiF,CAC1G,IAAM,EAAoB,CAAC,EACvB,EACJ,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAChD,IAAM,EAAM,EAAK,GACjB,GAAI,IAAQ,IAAA,GAAW,MACvB,GAAI,IAAQ,iBAEV,IADA,EAAc,EAAK,EAAE,GACjB,IAAgB,IAAA,GAAW,MAAU,MAAM,GAAG,EAAI,kBAAkB,CAAA,MACnE,GAAI,EAAI,WAAW,IAAI,EAC5B,MAAU,MAAM,mBAAmB,GAAK,OAExC,EAAQ,KAAK,CAAG,CAEpB,CACA,MAAO,CAAE,UAAS,aAAY,CAChC"}
@@ -0,0 +1,7 @@
1
+ export declare const VALIDATE_SUBCOMMANDS: readonly ['validate-problem', 'validate-course', 'validate-contest'];
2
+ export type ValidateSubcommand = (typeof VALIDATE_SUBCOMMANDS)[number];
3
+ /**
4
+ * Validate learning-material files without running any program and print one report per target.
5
+ * Returns the process exit code: 1 when any target has an error (warnings alone pass).
6
+ */
7
+ export declare function validateLearningMaterial(subcommand: ValidateSubcommand, args: readonly string[]): Promise<number>;
@@ -0,0 +1,2 @@
1
+ import{formatValidationReport as e}from"../learningMaterial/validationResult.js";import{validateContestFile as t}from"../learningMaterial/validateContest.js";import{validateCourseDirectory as n}from"../learningMaterial/validateCourse.js";import{validateProblemDirectory as r}from"../learningMaterial/validateProblem.js";const i=[`validate-problem`,`validate-course`,`validate-contest`];async function a(t,n){let{targets:r,problemsDir:i}=s(n);if(r.length===0)throw Error(`${t} requires at least one path`);if(t===`validate-problem`){if(i!==void 0)throw Error(`${t} does not take --problems-dir`)}else if(r.length>1)throw Error(`${t} accepts exactly one path, but got ${r.length}`);let a=!1;for(let n of r){let r=await o(t,n,i);process.stdout.write(e(n,r)),a||=r.errors.length>0}return+!!a}async function o(e,i,a){switch(e){case`validate-problem`:return r(i);case`validate-course`:return n(i,{problemsDirectoryPath:a});case`validate-contest`:return t(i,{problemsDirectoryPath:a})}}function s(e){let t=[],n;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--problems-dir`){if(n=e[++r],n===void 0)throw Error(`${i} requires a value`)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else t.push(i)}return{targets:t,problemsDir:n}}export{i as VALIDATE_SUBCOMMANDS,a as validateLearningMaterial};
2
+ //# sourceMappingURL=validateLearningMaterial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateLearningMaterial.js","names":[],"sources":["../../src/cli/validateLearningMaterial.ts"],"sourcesContent":["import { validateContestFile } from '../learningMaterial/validateContest.js';\nimport { validateCourseDirectory } from '../learningMaterial/validateCourse.js';\nimport { validateProblemDirectory } from '../learningMaterial/validateProblem.js';\nimport { formatValidationReport, type ValidationResult } from '../learningMaterial/validationResult.js';\n\nexport const VALIDATE_SUBCOMMANDS = ['validate-problem', 'validate-course', 'validate-contest'] as const;\n\nexport type ValidateSubcommand = (typeof VALIDATE_SUBCOMMANDS)[number];\n\n/**\n * Validate learning-material files without running any program and print one report per target.\n * Returns the process exit code: 1 when any target has an error (warnings alone pass).\n */\nexport async function validateLearningMaterial(\n subcommand: ValidateSubcommand,\n args: readonly string[]\n): Promise<number> {\n const { targets, problemsDir } = parseValidateArgs(args);\n if (targets.length === 0) throw new Error(`${subcommand} requires at least one path`);\n if (subcommand === 'validate-problem') {\n if (problemsDir !== undefined) throw new Error(`${subcommand} does not take --problems-dir`);\n } else if (targets.length > 1) {\n throw new Error(`${subcommand} accepts exactly one path, but got ${targets.length}`);\n }\n\n let hasError = false;\n for (const target of targets) {\n const result = await validate(subcommand, target, problemsDir);\n process.stdout.write(formatValidationReport(target, result));\n hasError ||= result.errors.length > 0;\n }\n return hasError ? 1 : 0;\n}\n\nasync function validate(\n subcommand: ValidateSubcommand,\n target: string,\n problemsDirectoryPath: string | undefined\n): Promise<ValidationResult> {\n switch (subcommand) {\n case 'validate-problem': {\n return validateProblemDirectory(target);\n }\n case 'validate-course': {\n return validateCourseDirectory(target, { problemsDirectoryPath });\n }\n case 'validate-contest': {\n return validateContestFile(target, { problemsDirectoryPath });\n }\n }\n}\n\nfunction parseValidateArgs(args: readonly string[]): { targets: string[]; problemsDir: string | undefined } {\n const targets: string[] = [];\n let problemsDir: string | undefined;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--problems-dir') {\n problemsDir = args[++index];\n if (problemsDir === undefined) throw new Error(`${arg} requires a value`);\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else {\n targets.push(arg);\n }\n }\n return { targets, problemsDir };\n}\n"],"mappings":"gUAKA,MAAa,EAAuB,CAAC,mBAAoB,kBAAmB,kBAAkB,EAQ9F,eAAsB,EACpB,EACA,EACiB,CACjB,GAAM,CAAE,UAAS,eAAgB,EAAkB,CAAI,EACvD,GAAI,EAAQ,SAAW,EAAG,MAAU,MAAM,GAAG,EAAW,4BAA4B,EACpF,GAAI,IAAe,mBACb,IAAA,IAAgB,IAAA,GAAW,MAAU,MAAM,GAAG,EAAW,8BAA8B,CAAA,MACtF,GAAI,EAAQ,OAAS,EAC1B,MAAU,MAAM,GAAG,EAAW,qCAAqC,EAAQ,QAAQ,EAGrF,IAAI,EAAW,GACf,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAS,MAAM,EAAS,EAAY,EAAQ,CAAW,EAC7D,QAAQ,OAAO,MAAM,EAAuB,EAAQ,CAAM,CAAC,EAC3D,IAAa,EAAO,OAAO,OAAS,CACtC,CACA,MAAO,IACT,CAEA,eAAe,EACb,EACA,EACA,EAC2B,CAC3B,OAAQ,EAAR,CACE,IAAK,mBACH,OAAO,EAAyB,CAAM,EAExC,IAAK,kBACH,OAAO,EAAwB,EAAQ,CAAE,uBAAsB,CAAC,EAElE,IAAK,mBACH,OAAO,EAAoB,EAAQ,CAAE,uBAAsB,CAAC,CAEhE,CACF,CAEA,SAAS,EAAkB,EAAiF,CAC1G,IAAM,EAAoB,CAAC,EACvB,EACJ,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAChD,IAAM,EAAM,EAAK,GACjB,GAAI,IAAQ,IAAA,GAAW,MACvB,GAAI,IAAQ,iBAEV,IADA,EAAc,EAAK,EAAE,GACjB,IAAgB,IAAA,GAAW,MAAU,MAAM,GAAG,EAAI,kBAAkB,CAAA,MACnE,GAAI,EAAI,WAAW,IAAI,EAC5B,MAAU,MAAM,mBAAmB,GAAK,OAExC,EAAQ,KAAK,CAAG,CAEpB,CACA,MAAO,CAAE,UAAS,aAAY,CAChC"}
@@ -1,14 +1,14 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/decisionCode.cjs"),r=require("../types/problem.cjs"),i=require("./findLanguageDefinitionByPath.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);async function s(e,s){let l=new Set,u=[];for(let n of await a.default.promises.readdir(e,{withFileTypes:!0,recursive:!0})){if(!n.isFile())continue;let r=o.default.relative(e,o.default.join(n.parentPath,n.name));l.add(r);let s=await a.default.promises.readFile(o.default.join(n.parentPath,n.name),`utf8`);if(s.includes(`�`))continue;let c=i.findLanguageDefinitionByPath(n.name);c&&u.push({path:r,data:c.grammer?t.removeCommentsInSourceCode(c.grammer,s):s})}if(s.requiredSubmissionFilePaths){let e=s.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:n.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/problem.cjs"),r=require("./findLanguageDefinitionByPath.cjs"),i=require("../types/decisionCode.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);async function s(e,s){let l=new Set,u=[];for(let n of await a.default.promises.readdir(e,{withFileTypes:!0,recursive:!0})){if(!n.isFile())continue;let i=o.default.relative(e,o.default.join(n.parentPath,n.name));l.add(i);let s=await a.default.promises.readFile(o.default.join(n.parentPath,n.name),`utf8`);if(s.includes(`�`))continue;let c=r.findLanguageDefinitionByPath(n.name);c&&u.push({path:i,data:c.grammer?t.removeCommentsInSourceCode(c.grammer,s):s})}if(s.requiredSubmissionFilePaths){let e=s.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:i.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
2
2
  次のファイルを追加してから再度提出してください。
3
3
 
4
4
  ${e.map(e=>`- \`${e}\``).join(`
5
5
  `)}
6
- `}}let d=[];for(let e of s.forbiddenRegExpsInCode??[]){let{pattern:t,message:n}=r.normalizeCodeRule(e),i=new RegExp(t,`g`),a=u.flatMap(e=>[...e.data.matchAll(i)].map(t=>({path:e.path,match:t[0]})));a.length>0&&d.push({label:n??`禁止パターン \`${i.toString()}\``,matches:a})}for(let e of s.forbiddenTextsInCode??[]){let{pattern:t,message:n}=r.normalizeCodeRule(e),i=u.filter(e=>e.data.includes(t)).map(e=>({path:e.path,match:t}));i.length>0&&d.push({label:n??`禁止文字列 \`${t}\``,matches:i})}if(d.length>0)return{decisionCode:n.DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
6
+ `}}let d=[];for(let e of s.forbiddenRegExpsInCode??[]){let{pattern:t,message:r}=n.normalizeCodeRule(e),i=new RegExp(t,`g`),a=u.flatMap(e=>[...e.data.matchAll(i)].map(t=>({path:e.path,match:t[0]})));a.length>0&&d.push({label:r??`禁止パターン \`${i.toString()}\``,matches:a})}for(let e of s.forbiddenTextsInCode??[]){let{pattern:t,message:r}=n.normalizeCodeRule(e),i=u.filter(e=>e.data.includes(t)).map(e=>({path:e.path,match:t}));i.length>0&&d.push({label:r??`禁止文字列 \`${t}\``,matches:i})}if(d.length>0)return{decisionCode:i.DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
7
7
  ソースコードを修正してから再度提出してください。
8
8
 
9
9
  ${d.map(c).join(`
10
10
  `)}
11
- `};let f=[];for(let e of s.requiredRegExpsInCode??[]){let{pattern:t,message:n}=r.normalizeCodeRule(e),i=new RegExp(t);u.some(e=>i.test(e.data))||f.push(n??`\`${i.toString()}\``)}if(f.length>0)return{decisionCode:n.DecisionCode.REQUIRED_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に必要な文字列が含まれていません。
11
+ `};let f=[];for(let e of s.requiredRegExpsInCode??[]){let{pattern:t,message:r}=n.normalizeCodeRule(e),i=new RegExp(t);u.some(e=>i.test(e.data))||f.push(r??`\`${i.toString()}\``)}if(f.length>0)return{decisionCode:i.DecisionCode.REQUIRED_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に必要な文字列が含まれていません。
12
12
  ソースコードを修正してから再度提出してください。
13
13
 
14
14
  ${f.map(e=>`- ${e}`).join(`
@@ -1,14 +1,14 @@
1
- import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{DecisionCode as t}from"../types/decisionCode.js";import{normalizeCodeRule as n}from"../types/problem.js";import{findLanguageDefinitionByPath as r}from"./findLanguageDefinitionByPath.js";import i from"node:fs";import a from"node:path";async function o(o,c){let l=new Set,u=[];for(let t of await i.promises.readdir(o,{withFileTypes:!0,recursive:!0})){if(!t.isFile())continue;let n=a.relative(o,a.join(t.parentPath,t.name));l.add(n);let s=await i.promises.readFile(a.join(t.parentPath,t.name),`utf8`);if(s.includes(`�`))continue;let c=r(t.name);c&&u.push({path:n,data:c.grammer?e(c.grammer,s):s})}if(c.requiredSubmissionFilePaths){let e=c.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:t.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
1
+ import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{normalizeCodeRule as t}from"../types/problem.js";import{findLanguageDefinitionByPath as n}from"./findLanguageDefinitionByPath.js";import{DecisionCode as r}from"../types/decisionCode.js";import i from"node:fs";import a from"node:path";async function o(o,c){let l=new Set,u=[];for(let t of await i.promises.readdir(o,{withFileTypes:!0,recursive:!0})){if(!t.isFile())continue;let r=a.relative(o,a.join(t.parentPath,t.name));l.add(r);let s=await i.promises.readFile(a.join(t.parentPath,t.name),`utf8`);if(s.includes(`�`))continue;let c=n(t.name);c&&u.push({path:r,data:c.grammer?e(c.grammer,s):s})}if(c.requiredSubmissionFilePaths){let e=c.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:r.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
2
2
  次のファイルを追加してから再度提出してください。
3
3
 
4
4
  ${e.map(e=>`- \`${e}\``).join(`
5
5
  `)}
6
- `}}let d=[];for(let e of c.forbiddenRegExpsInCode??[]){let{pattern:t,message:r}=n(e),i=new RegExp(t,`g`),a=u.flatMap(e=>[...e.data.matchAll(i)].map(t=>({path:e.path,match:t[0]})));a.length>0&&d.push({label:r??`禁止パターン \`${i.toString()}\``,matches:a})}for(let e of c.forbiddenTextsInCode??[]){let{pattern:t,message:r}=n(e),i=u.filter(e=>e.data.includes(t)).map(e=>({path:e.path,match:t}));i.length>0&&d.push({label:r??`禁止文字列 \`${t}\``,matches:i})}if(d.length>0)return{decisionCode:t.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
6
+ `}}let d=[];for(let e of c.forbiddenRegExpsInCode??[]){let{pattern:n,message:r}=t(e),i=new RegExp(n,`g`),a=u.flatMap(e=>[...e.data.matchAll(i)].map(t=>({path:e.path,match:t[0]})));a.length>0&&d.push({label:r??`禁止パターン \`${i.toString()}\``,matches:a})}for(let e of c.forbiddenTextsInCode??[]){let{pattern:n,message:r}=t(e),i=u.filter(e=>e.data.includes(n)).map(e=>({path:e.path,match:n}));i.length>0&&d.push({label:r??`禁止文字列 \`${n}\``,matches:i})}if(d.length>0)return{decisionCode:r.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
7
7
  ソースコードを修正してから再度提出してください。
8
8
 
9
9
  ${d.map(s).join(`
10
10
  `)}
11
- `};let f=[];for(let e of c.requiredRegExpsInCode??[]){let{pattern:t,message:r}=n(e),i=new RegExp(t);u.some(e=>i.test(e.data))||f.push(r??`\`${i.toString()}\``)}if(f.length>0)return{decisionCode:t.REQUIRED_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に必要な文字列が含まれていません。
11
+ `};let f=[];for(let e of c.requiredRegExpsInCode??[]){let{pattern:n,message:r}=t(e),i=new RegExp(n);u.some(e=>i.test(e.data))||f.push(r??`\`${i.toString()}\``)}if(f.length>0)return{decisionCode:r.REQUIRED_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に必要な文字列が含まれていません。
12
12
  ソースコードを修正してから再度提出してください。
13
13
 
14
14
  ${f.map(e=>`- ${e}`).join(`
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./temporaryProblemDirCopy.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("node:fs/promises");r=e.__toESM(r,1);let i=require("node:child_process");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);const o={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},s={bun:p,cargo:m,go:h,gradle:g,maven:_,npm:v,pnpm:y,ruby:b,uv:x,yarn:S},c=A();async function l(e){let i=await r.default.mkdtemp(n.default.join(a.default.tmpdir(),e.tempDirPrefix??`exercode-`));try{await r.default.cp(e.cwd,i,{recursive:!0}),await D({packageManager:e.packageManager,projectDir:e.projectDir,runDir:i,projectFilePaths:e.projectFilePaths});let t=e.env?{...process.env,...e.env}:process.env,n=e.prepareDependencies===!1?void 0:await d(e.packageManager,i),a=typeof e.command==`function`?e.command({runDir:i}):e.command,o=Date.now(),s=e.outputLimitBytes??52428800,c;if(n&&(c=await k(n,{cwd:i,env:t,outputLimitBytes:s,stdin:``,timeLimitSeconds:e.timeLimitSeconds}),f(c)))return u({elapsedTimeSeconds:(Date.now()-o)/1e3,options:e,result:c});let l=e.timeLimitSeconds-(Date.now()-o)/1e3;if(l<=0)return{stdin:e.stdin??``,stdout:c?.stdout??``,stderr:c?.stderr??``,status:0,timeSeconds:e.timeLimitSeconds+.001,memoryBytes:c?.memoryBytes??0,timedOut:!0,signal:c?.signal,outputLimitExceeded:!1};let p=await k(a,{cwd:i,env:t,outputLimitBytes:s,stdin:e.stdin??``,timeLimitSeconds:l}),m=(Date.now()-o)/1e3;return u(c?{elapsedTimeSeconds:m,options:e,result:{...p,timeSeconds:c.timeSeconds+p.timeSeconds,memoryBytes:Math.max(c.memoryBytes,p.memoryBytes)}}:{elapsedTimeSeconds:m,options:e,result:p})}finally{await t.forciblyRemoveDirectory(i)}}function u(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function d(e,t){return s[e](t)}function f(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function p(e){if(await T(n.default.join(e,`package.json`)))return await w(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function m(e){if(await T(n.default.join(e,`Cargo.toml`)))return await T(n.default.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function h(e){if(await T(n.default.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function g(e){if(!await w(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await T(n.default.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await T(n.default.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function _(e){if(!await T(n.default.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await T(n.default.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await T(n.default.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function v(e){if(await T(n.default.join(e,`package.json`)))return await T(n.default.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function y(e){if(await T(n.default.join(e,`package.json`)))return await T(n.default.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function b(e){if(await T(n.default.join(e,`Gemfile`)))return await T(n.default.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function x(){}async function S(e){if(!await T(n.default.join(e,`package.json`)))return;let t=await C(e),r=await T(n.default.join(e,`yarn.lock`));return t?r?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:r?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function C(e){if(await T(n.default.join(e,`.yarnrc.yml`)))return!0;let t=await E(n.default.join(e,`package.json`)),r=typeof t.packageManager==`string`?t.packageManager:void 0,i=/^yarn@(\d+)/.exec(r??``)?.[1];return i!==void 0&&Number(i)>=2}async function w(e,t){for(let r of t)if(await T(n.default.join(e,r)))return!0;return!1}async function T(e){try{return await r.default.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function E(e){try{let t=JSON.parse(await r.default.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function D(e){for(let t of e.projectFilePaths??o[e.packageManager])await O(n.default.join(e.projectDir,t),n.default.join(e.runDir,t))}async function O(e,t){try{await r.default.lstat(e),await r.default.mkdir(n.default.dirname(t),{recursive:!0}),await r.default.cp(e,t,{force:!0,recursive:!0})}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function k(e,t){let r=c===void 0?void 0:n.default.join(t.cwd,`.exercode-time-result`),a=c===void 0?e:[...c,`--output=${r}`,...e],o=i.default.spawn(a[0],a.slice(1),{cwd:t.cwd,detached:process.platform!==`win32`,env:t.env,stdio:[`pipe`,`pipe`,`pipe`]}),s=[],l=[],u=0,d=!1,f=!1,p=(e,n)=>{if(u>=t.outputLimitBytes){n.byteLength>0&&(f=!0,j(o,`SIGKILL`));return}let r=t.outputLimitBytes-u,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),u+=i.byteLength,n.byteLength>r&&(f=!0,j(o,`SIGKILL`))};o.stdout.on(`data`,e=>p(s,e)),o.stderr.on(`data`,e=>p(l,e));let m=setTimeout(()=>{d=!0,j(o,`SIGTERM`)},t.timeLimitSeconds*1e3),h=setTimeout(()=>{d&&j(o,`SIGKILL`)},t.timeLimitSeconds*1e3+1e3);h.unref();let{status:g,signal:_}=await new Promise((e,n)=>{let r=!1,i,a=e=>{r||(i=e,j(o,`SIGKILL`),o.pid===void 0&&(r=!0,n(e)))};o.on(`error`,a),o.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),o.on(`close`,(t,a)=>{if(!r){if(r=!0,i){n(i);return}e({status:t??void 0,signal:a??void 0})}}),o.stdin.end(t.stdin)}).finally(()=>{clearTimeout(m),clearTimeout(h)}),{timeSeconds:v,memoryBytes:y}=r===void 0?{timeSeconds:0,memoryBytes:0}:await N(r);return{stdout:Buffer.concat(s).toString(),stderr:Buffer.concat(l).toString(),status:g,timeSeconds:v,memoryBytes:y,timedOut:d,signal:_,outputLimitExceeded:f}}function A(){let e=a.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=i.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function j(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}function M(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function N(e){let t;try{t=await r.default.readFile(e,`utf8`)}catch(e){if(M(e,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw e}let n=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return n?{timeSeconds:Number(n[1].replace(`,`,`.`)),memoryBytes:Number(n[2])*1024}:{timeSeconds:0,memoryBytes:0}}exports.copyPackageManagerProjectFiles=D,exports.runCommandInTemporaryPackageManagerProject=l;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./spawnWithLimits.cjs"),n=require("./temporaryProblemDirCopy.cjs");let r=require("node:path");r=e.__toESM(r,1);let i=require("node:fs/promises");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);const o={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},s={bun:f,cargo:p,go:m,gradle:h,maven:g,npm:_,pnpm:v,ruby:y,uv:b,yarn:x};async function c(e){let o=await i.default.mkdtemp(r.default.join(a.default.tmpdir(),e.tempDirPrefix??`exercode-`));try{await i.default.cp(e.cwd,o,{recursive:!0}),await E({packageManager:e.packageManager,projectDir:e.projectDir,runDir:o,projectFilePaths:e.projectFilePaths});let n=e.env?{...process.env,...e.env}:process.env,r=e.prepareDependencies===!1?void 0:await u(e.packageManager,o),a=typeof e.command==`function`?e.command({runDir:o}):e.command,s=e.outputLimitBytes??52428800,c;if(r&&(c=await t.spawnWithLimits(r,{cwd:o,env:n,outputLimitBytes:s,stdin:``,timeLimitSeconds:e.timeLimitSeconds}),d(c)))return l({options:e,result:c});let f=e.timeLimitSeconds-(c?.timeSeconds??0);if(f<=0)return{stdin:e.stdin??``,stdout:c?.stdout??``,stderr:c?.stderr??``,status:0,timeSeconds:e.timeLimitSeconds+.001,memoryBytes:c?.memoryBytes??0,timedOut:!0,signal:c?.signal,outputLimitExceeded:!1};let p=await t.spawnWithLimits(a,{cwd:o,env:n,outputLimitBytes:s,stdin:e.stdin??``,timeLimitSeconds:f});return l(c?{options:e,result:{...p,timeSeconds:c.timeSeconds+p.timeSeconds,memoryBytes:Math.max(c.memoryBytes,p.memoryBytes)}}:{options:e,result:p})}finally{await n.forciblyRemoveDirectory(o)}}function l(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function u(e,t){return s[e](t)}function d(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function f(e){if(await w(r.default.join(e,`package.json`)))return await C(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function p(e){if(await w(r.default.join(e,`Cargo.toml`)))return await w(r.default.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function m(e){if(await w(r.default.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function h(e){if(!await C(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await w(r.default.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await w(r.default.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function g(e){if(!await w(r.default.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await w(r.default.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await w(r.default.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function _(e){if(await w(r.default.join(e,`package.json`)))return await w(r.default.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function v(e){if(await w(r.default.join(e,`package.json`)))return await w(r.default.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function y(e){if(await w(r.default.join(e,`Gemfile`)))return await w(r.default.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function b(){}async function x(e){if(!await w(r.default.join(e,`package.json`)))return;let t=await S(e),n=await w(r.default.join(e,`yarn.lock`));return t?n?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:n?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function S(e){if(await w(r.default.join(e,`.yarnrc.yml`)))return!0;let t=await T(r.default.join(e,`package.json`)),n=typeof t.packageManager==`string`?t.packageManager:void 0,i=/^yarn@(\d+)/.exec(n??``)?.[1];return i!==void 0&&Number(i)>=2}async function C(e,t){for(let n of t)if(await w(r.default.join(e,n)))return!0;return!1}async function w(e){try{return await i.default.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function T(e){try{let t=JSON.parse(await i.default.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function E(e){for(let t of e.projectFilePaths??o[e.packageManager])await D(r.default.join(e.projectDir,t),r.default.join(e.runDir,t))}async function D(e,t){try{await i.default.lstat(e),await i.default.mkdir(r.default.dirname(t),{recursive:!0}),await i.default.cp(e,t,{force:!0,recursive:!0})}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}exports.copyPackageManagerProjectFiles=E,exports.runCommandInTemporaryPackageManagerProject=c;
2
2
  //# sourceMappingURL=runCommandInTemporaryPackageManagerProject.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runCommandInTemporaryPackageManagerProject.cjs","names":["fs","path","os","forciblyRemoveDirectory","childProcess"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\nconst killGracePeriodMilliseconds = 1000;\nconst timeCommand = resolveTimeCommand();\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const startedAt = Date.now();\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: Awaited<ReturnType<typeof spawnWithInput>> | undefined;\n\n if (installCommand) {\n installResult = await spawnWithInput(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n elapsedTimeSeconds: (Date.now() - startedAt) / 1000,\n options,\n result: installResult,\n });\n }\n }\n\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (Date.now() - startedAt) / 1000;\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithInput(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n const elapsedTimeSeconds = (Date.now() - startedAt) / 1000;\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n elapsedTimeSeconds,\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ elapsedTimeSeconds, options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n elapsedTimeSeconds: number;\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: Awaited<ReturnType<typeof spawnWithInput>>;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut\n ? context.options.timeLimitSeconds + 1e-3\n : context.result.timeSeconds || context.elapsedTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: Awaited<ReturnType<typeof spawnWithInput>>): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code === 'ENOENT') return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n }\n}\n\nasync function spawnWithInput(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<{\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}> {\n const timeOutputPath = timeCommand === undefined ? undefined : path.join(context.cwd, '.exercode-time-result');\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined ? command : [...timeCommand, `--output=${timeOutputPath}`, ...command];\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached: process.platform !== 'win32',\n env: context.env,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, context.timeLimitSeconds * 1000);\n const killTimeout = setTimeout(\n () => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n },\n context.timeLimitSeconds * 1000 + killGracePeriodMilliseconds\n );\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n if (subprocess.pid === undefined) {\n settled = true;\n reject(error);\n }\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('close', (code, closeSignal) => {\n if (settled) return;\n settled = true;\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: closeSignal ?? undefined });\n });\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n });\n\n const { timeSeconds, memoryBytes } =\n timeOutputPath === undefined ? { timeSeconds: 0, memoryBytes: 0 } : await readTimeResult(timeOutputPath);\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: Buffer.concat(stderrChunks).toString(),\n status,\n timeSeconds,\n memoryBytes,\n timedOut,\n signal,\n outputLimitExceeded,\n };\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n return [command, '--format', '%e %M'];\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ESRCH' && code !== 'EPERM') throw error;\n }\n}\n\nfunction isErrorWithCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && (error as { code: unknown }).code === code;\n}\n\nasync function readTimeResult(timeOutputPath: string): Promise<{ timeSeconds: number; memoryBytes: number }> {\n // An absent file means \"no measurement\" (the command was killed before `time` wrote one). Anything\n // else is read as-is: the submission shares this process's OS user, so a symlink or FIFO planted\n // here only affects its own run, which the judge server's request timeout ends.\n let content: string;\n try {\n content = await fs.readFile(timeOutputPath, 'utf8');\n } catch (error) {\n if (isErrorWithCode(error, 'ENOENT')) return { timeSeconds: 0, memoryBytes: 0 };\n throw error;\n }\n\n const match = /(\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return { timeSeconds: 0, memoryBytes: 0 };\n\n return { timeSeconds: Number(match[1]!.replace(',', '.')), memoryBytes: Number(match[2]) * 1024 };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runCommandInTemporaryPackageManagerProject.cjs","names":["fs","path","os","spawnWithLimits","forciblyRemoveDirectory"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code === 'ENOENT') return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{forciblyRemoveDirectory as e}from"./temporaryProblemDirCopy.js";import t from"node:path";import n from"node:fs/promises";import r from"node:child_process";import i from"node:os";const a={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},o={bun:f,cargo:p,go:m,gradle:h,maven:g,npm:_,pnpm:v,ruby:y,uv:b,yarn:x},s=k();async function c(r){let a=await n.mkdtemp(t.join(i.tmpdir(),r.tempDirPrefix??`exercode-`));try{await n.cp(r.cwd,a,{recursive:!0}),await E({packageManager:r.packageManager,projectDir:r.projectDir,runDir:a,projectFilePaths:r.projectFilePaths});let e=r.env?{...process.env,...r.env}:process.env,t=r.prepareDependencies===!1?void 0:await u(r.packageManager,a),i=typeof r.command==`function`?r.command({runDir:a}):r.command,o=Date.now(),s=r.outputLimitBytes??52428800,c;if(t&&(c=await O(t,{cwd:a,env:e,outputLimitBytes:s,stdin:``,timeLimitSeconds:r.timeLimitSeconds}),d(c)))return l({elapsedTimeSeconds:(Date.now()-o)/1e3,options:r,result:c});let f=r.timeLimitSeconds-(Date.now()-o)/1e3;if(f<=0)return{stdin:r.stdin??``,stdout:c?.stdout??``,stderr:c?.stderr??``,status:0,timeSeconds:r.timeLimitSeconds+.001,memoryBytes:c?.memoryBytes??0,timedOut:!0,signal:c?.signal,outputLimitExceeded:!1};let p=await O(i,{cwd:a,env:e,outputLimitBytes:s,stdin:r.stdin??``,timeLimitSeconds:f}),m=(Date.now()-o)/1e3;return l(c?{elapsedTimeSeconds:m,options:r,result:{...p,timeSeconds:c.timeSeconds+p.timeSeconds,memoryBytes:Math.max(c.memoryBytes,p.memoryBytes)}}:{elapsedTimeSeconds:m,options:r,result:p})}finally{await e(a)}}function l(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function u(e,t){return o[e](t)}function d(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function f(e){if(await w(t.join(e,`package.json`)))return await C(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function p(e){if(await w(t.join(e,`Cargo.toml`)))return await w(t.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function m(e){if(await w(t.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function h(e){if(!await C(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let n=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await w(t.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...n]:[`gradle`,...n]:await w(t.join(e,`gradlew`))?[`sh`,`./gradlew`,...n]:[`gradle`,...n]}async function g(e){if(!await w(t.join(e,`pom.xml`)))return;let n=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await w(t.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...n]:[`mvn`,...n]:await w(t.join(e,`mvnw`))?[`sh`,`./mvnw`,...n]:[`mvn`,...n]}async function _(e){if(await w(t.join(e,`package.json`)))return await w(t.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function v(e){if(await w(t.join(e,`package.json`)))return await w(t.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function y(e){if(await w(t.join(e,`Gemfile`)))return await w(t.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function b(){}async function x(e){if(!await w(t.join(e,`package.json`)))return;let n=await S(e),r=await w(t.join(e,`yarn.lock`));return n?r?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:r?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function S(e){if(await w(t.join(e,`.yarnrc.yml`)))return!0;let n=await T(t.join(e,`package.json`)),r=typeof n.packageManager==`string`?n.packageManager:void 0,i=/^yarn@(\d+)/.exec(r??``)?.[1];return i!==void 0&&Number(i)>=2}async function C(e,n){for(let r of n)if(await w(t.join(e,r)))return!0;return!1}async function w(e){try{return await n.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function T(e){try{let t=JSON.parse(await n.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function E(e){for(let n of e.projectFilePaths??a[e.packageManager])await D(t.join(e.projectDir,n),t.join(e.runDir,n))}async function D(e,r){try{await n.lstat(e),await n.mkdir(t.dirname(r),{recursive:!0}),await n.cp(e,r,{force:!0,recursive:!0})}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function O(e,n){let i=s===void 0?void 0:t.join(n.cwd,`.exercode-time-result`),a=s===void 0?e:[...s,`--output=${i}`,...e],o=r.spawn(a[0],a.slice(1),{cwd:n.cwd,detached:process.platform!==`win32`,env:n.env,stdio:[`pipe`,`pipe`,`pipe`]}),c=[],l=[],u=0,d=!1,f=!1,p=(e,t)=>{if(u>=n.outputLimitBytes){t.byteLength>0&&(f=!0,A(o,`SIGKILL`));return}let r=n.outputLimitBytes-u,i=t.byteLength>r?t.subarray(0,r):t;e.push(i),u+=i.byteLength,t.byteLength>r&&(f=!0,A(o,`SIGKILL`))};o.stdout.on(`data`,e=>p(c,e)),o.stderr.on(`data`,e=>p(l,e));let m=setTimeout(()=>{d=!0,A(o,`SIGTERM`)},n.timeLimitSeconds*1e3),h=setTimeout(()=>{d&&A(o,`SIGKILL`)},n.timeLimitSeconds*1e3+1e3);h.unref();let{status:g,signal:_}=await new Promise((e,t)=>{let r=!1,i,a=e=>{r||(i=e,A(o,`SIGKILL`),o.pid===void 0&&(r=!0,t(e)))};o.on(`error`,a),o.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),o.on(`close`,(n,a)=>{if(!r){if(r=!0,i){t(i);return}e({status:n??void 0,signal:a??void 0})}}),o.stdin.end(n.stdin)}).finally(()=>{clearTimeout(m),clearTimeout(h)}),{timeSeconds:v,memoryBytes:y}=i===void 0?{timeSeconds:0,memoryBytes:0}:await M(i);return{stdout:Buffer.concat(c).toString(),stderr:Buffer.concat(l).toString(),status:g,timeSeconds:v,memoryBytes:y,timedOut:d,signal:_,outputLimitExceeded:f}}function k(){let e=i.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=r.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function A(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}function j(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function M(e){let t;try{t=await n.readFile(e,`utf8`)}catch(e){if(j(e,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw e}let r=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return r?{timeSeconds:Number(r[1].replace(`,`,`.`)),memoryBytes:Number(r[2])*1024}:{timeSeconds:0,memoryBytes:0}}export{E as copyPackageManagerProjectFiles,c as runCommandInTemporaryPackageManagerProject};
1
+ import{spawnWithLimits as e}from"./spawnWithLimits.js";import{forciblyRemoveDirectory as t}from"./temporaryProblemDirCopy.js";import n from"node:path";import r from"node:fs/promises";import i from"node:os";const a={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},o={bun:d,cargo:f,go:p,gradle:m,maven:h,npm:g,pnpm:_,ruby:v,uv:y,yarn:b};async function s(a){let o=await r.mkdtemp(n.join(i.tmpdir(),a.tempDirPrefix??`exercode-`));try{await r.cp(a.cwd,o,{recursive:!0}),await T({packageManager:a.packageManager,projectDir:a.projectDir,runDir:o,projectFilePaths:a.projectFilePaths});let t=a.env?{...process.env,...a.env}:process.env,n=a.prepareDependencies===!1?void 0:await l(a.packageManager,o),i=typeof a.command==`function`?a.command({runDir:o}):a.command,s=a.outputLimitBytes??52428800,d;if(n&&(d=await e(n,{cwd:o,env:t,outputLimitBytes:s,stdin:``,timeLimitSeconds:a.timeLimitSeconds}),u(d)))return c({options:a,result:d});let f=a.timeLimitSeconds-(d?.timeSeconds??0);if(f<=0)return{stdin:a.stdin??``,stdout:d?.stdout??``,stderr:d?.stderr??``,status:0,timeSeconds:a.timeLimitSeconds+.001,memoryBytes:d?.memoryBytes??0,timedOut:!0,signal:d?.signal,outputLimitExceeded:!1};let p=await e(i,{cwd:o,env:t,outputLimitBytes:s,stdin:a.stdin??``,timeLimitSeconds:f});return c(d?{options:a,result:{...p,timeSeconds:d.timeSeconds+p.timeSeconds,memoryBytes:Math.max(d.memoryBytes,p.memoryBytes)}}:{options:a,result:p})}finally{await t(o)}}function c(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function l(e,t){return o[e](t)}function u(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function d(e){if(await C(n.join(e,`package.json`)))return await S(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function f(e){if(await C(n.join(e,`Cargo.toml`)))return await C(n.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function p(e){if(await C(n.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function m(e){if(!await S(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await C(n.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await C(n.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function h(e){if(!await C(n.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await C(n.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await C(n.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function g(e){if(await C(n.join(e,`package.json`)))return await C(n.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function _(e){if(await C(n.join(e,`package.json`)))return await C(n.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function v(e){if(await C(n.join(e,`Gemfile`)))return await C(n.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function y(){}async function b(e){if(!await C(n.join(e,`package.json`)))return;let t=await x(e),r=await C(n.join(e,`yarn.lock`));return t?r?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:r?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function x(e){if(await C(n.join(e,`.yarnrc.yml`)))return!0;let t=await w(n.join(e,`package.json`)),r=typeof t.packageManager==`string`?t.packageManager:void 0,i=/^yarn@(\d+)/.exec(r??``)?.[1];return i!==void 0&&Number(i)>=2}async function S(e,t){for(let r of t)if(await C(n.join(e,r)))return!0;return!1}async function C(e){try{return await r.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function w(e){try{let t=JSON.parse(await r.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function T(e){for(let t of e.projectFilePaths??a[e.packageManager])await E(n.join(e.projectDir,t),n.join(e.runDir,t))}async function E(e,t){try{await r.lstat(e),await r.mkdir(n.dirname(t),{recursive:!0}),await r.cp(e,t,{force:!0,recursive:!0})}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}export{T as copyPackageManagerProjectFiles,s as runCommandInTemporaryPackageManagerProject};
2
2
  //# sourceMappingURL=runCommandInTemporaryPackageManagerProject.js.map