@exercode/problem-utils 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/javaJudge.cjs +2 -0
- package/dist/helpers/javaJudge.cjs.map +1 -0
- package/dist/helpers/javaJudge.d.ts +8 -0
- package/dist/helpers/javaJudge.js +2 -0
- package/dist/helpers/javaJudge.js.map +1 -0
- package/dist/helpers/startHttpServer.cjs +1 -1
- package/dist/helpers/startHttpServer.cjs.map +1 -1
- package/dist/helpers/startHttpServer.d.ts +3 -3
- package/dist/helpers/startHttpServer.js +1 -1
- package/dist/helpers/startHttpServer.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/presets/springBoot.cjs +9 -0
- package/dist/presets/springBoot.cjs.map +1 -0
- package/dist/presets/springBoot.d.ts +6 -0
- package/dist/presets/springBoot.js +9 -0
- package/dist/presets/springBoot.js.map +1 -0
- package/dist/presets/tomcat.cjs +4 -4
- package/dist/presets/tomcat.cjs.map +1 -1
- package/dist/presets/tomcat.js +5 -5
- package/dist/presets/tomcat.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("zod");const i=5e4;async function a(e,r){let i=await t.default.promises.readFile(e,`utf8`),a=/^\s*package\s+([a-zA-Z0-9_.]+)\s*;/m.exec(i);return a?.[1]?n.default.join(a[1].replaceAll(`.`,`/`),n.default.basename(r)):r}async function o(e){let t=[];return await s(e,``,t),t.toSorted()}async function s(e,r,i){let a=r?n.default.join(e,r):e,o=await t.default.promises.readdir(a,{withFileTypes:!0});for(let t of o){let a=r?n.default.join(r,t.name):t.name;if(t.isDirectory()){await s(e,a,i);continue}t.isFile()&&i.push(a)}}function c(e){if(!t.default.existsSync(e))return;let n=JSON.parse(t.default.readFileSync(e,`utf8`)),i=r.z.array(r.z.unknown()).safeParse(n);if(!i.success)return;let a=r.z.object({path:r.z.string(),data:r.z.string(),encoding:r.z.literal(`base64`).optional()});return i.data.flatMap(e=>{let t=a.safeParse(e);return t.success?[t.data]:[]})}function l(e){return e.slice(0,i)}exports.MAX_OUTPUT_LENGTH=i,exports.listSubmissionFiles=o,exports.readOutputFiles=c,exports.resolveJavaRelativePath=a,exports.truncateOutput=l;
|
|
2
|
+
//# sourceMappingURL=javaJudge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"javaJudge.cjs","names":["fs","path","z"],"sources":["../../src/helpers/javaJudge.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\nexport const MAX_OUTPUT_LENGTH = 50_000;\n\nexport async function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nexport async function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nexport function readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nexport function truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n"],"mappings":"6KAMA,MAAa,EAAoB,IAEjC,eAAsB,EAAwB,EAAoB,EAA+C,CAC/G,IAAM,EAAa,MAAMA,EAAAA,QAAG,SAAS,SAAS,EAAY,MAAM,EAC1D,EAAe,sCAAsC,KAAK,CAAU,EAK1E,OAJK,IAAe,GAIbC,EAAAA,QAAK,KAAK,EAAa,EAAE,CAAC,WAAW,IAAK,GAAG,EAAGA,EAAAA,QAAK,SAAS,CAAoB,CAAC,EAHjF,CAIX,CAEA,eAAsB,EAAoB,EAAoC,CAC5E,IAAM,EAAsB,CAAC,EAE7B,OADA,MAAM,EAAuB,EAAS,GAAI,CAAS,EAC5C,EAAU,SAAS,CAC5B,CAEA,eAAe,EAAuB,EAAiB,EAAqB,EAAoC,CAC9G,IAAM,EAAa,EAAcA,EAAAA,QAAK,KAAK,EAAS,CAAW,EAAI,EAC7D,EAAU,MAAMD,EAAAA,QAAG,SAAS,QAAQ,EAAY,CAAE,cAAe,EAAK,CAAC,EAC7E,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAoB,EAAcC,EAAAA,QAAK,KAAK,EAAa,EAAM,IAAI,EAAI,EAAM,KACnF,GAAI,EAAM,YAAY,EAAG,CACvB,MAAM,EAAuB,EAAS,EAAmB,CAAS,EAClE,QACF,CACI,EAAM,OAAO,GACf,EAAU,KAAK,CAAiB,CAEpC,CACF,CAEA,SAAgB,EAAgB,EAA4C,CAC1E,GAAI,CAACD,EAAAA,QAAG,WAAW,CAAQ,EACzB,OAIF,IAAM,EAAkB,KAAK,MAAMA,EAAAA,QAAG,aAAa,EAAU,MAAM,CAAC,EAC9D,EAASE,EAAAA,EAAE,MAAMA,EAAAA,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAM,EACpD,GAAI,CAAC,EAAO,QAAS,OACrB,IAAM,EAAmBA,EAAAA,EAAE,OAAO,CAChC,KAAMA,EAAAA,EAAE,OAAO,EACf,KAAMA,EAAAA,EAAE,OAAO,EACf,SAAUA,EAAAA,EAAE,QAAQ,QAAQ,CAAC,CAAC,SAAS,CACzC,CAAC,EACD,OAAO,EAAO,KAAK,QAAS,GAAU,CACpC,IAAM,EAAS,EAAiB,UAAU,CAAK,EAC/C,OAAO,EAAO,QAAU,CAAC,EAAO,IAAI,EAAI,CAAC,CAC3C,CAAC,CACH,CAEA,SAAgB,EAAe,EAAuB,CACpD,OAAO,EAAM,MAAM,EAAG,CAAiB,CACzC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TestCaseResult } from '../types/testCaseResult.js';
|
|
2
|
+
type OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];
|
|
3
|
+
export declare const MAX_OUTPUT_LENGTH = 50000;
|
|
4
|
+
export declare function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string>;
|
|
5
|
+
export declare function listSubmissionFiles(rootDir: string): Promise<string[]>;
|
|
6
|
+
export declare function readOutputFiles(filePath: string): OutputFile[] | undefined;
|
|
7
|
+
export declare function truncateOutput(value: string): string;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{z as n}from"zod";const r=5e4;async function i(n,r){let i=await e.promises.readFile(n,`utf8`),a=/^\s*package\s+([a-zA-Z0-9_.]+)\s*;/m.exec(i);return a?.[1]?t.join(a[1].replaceAll(`.`,`/`),t.basename(r)):r}async function a(e){let t=[];return await o(e,``,t),t.toSorted()}async function o(n,r,i){let a=r?t.join(n,r):n,s=await e.promises.readdir(a,{withFileTypes:!0});for(let e of s){let a=r?t.join(r,e.name):e.name;if(e.isDirectory()){await o(n,a,i);continue}e.isFile()&&i.push(a)}}function s(t){if(!e.existsSync(t))return;let r=JSON.parse(e.readFileSync(t,`utf8`)),i=n.array(n.unknown()).safeParse(r);if(!i.success)return;let a=n.object({path:n.string(),data:n.string(),encoding:n.literal(`base64`).optional()});return i.data.flatMap(e=>{let t=a.safeParse(e);return t.success?[t.data]:[]})}function c(e){return e.slice(0,r)}export{r as MAX_OUTPUT_LENGTH,a as listSubmissionFiles,s as readOutputFiles,i as resolveJavaRelativePath,c as truncateOutput};
|
|
2
|
+
//# sourceMappingURL=javaJudge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"javaJudge.js","names":[],"sources":["../../src/helpers/javaJudge.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\nexport const MAX_OUTPUT_LENGTH = 50_000;\n\nexport async function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nexport async function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nexport function readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nexport function truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n"],"mappings":"wEAMA,MAAa,EAAoB,IAEjC,eAAsB,EAAwB,EAAoB,EAA+C,CAC/G,IAAM,EAAa,MAAM,EAAG,SAAS,SAAS,EAAY,MAAM,EAC1D,EAAe,sCAAsC,KAAK,CAAU,EAK1E,OAJK,IAAe,GAIb,EAAK,KAAK,EAAa,EAAE,CAAC,WAAW,IAAK,GAAG,EAAG,EAAK,SAAS,CAAoB,CAAC,EAHjF,CAIX,CAEA,eAAsB,EAAoB,EAAoC,CAC5E,IAAM,EAAsB,CAAC,EAE7B,OADA,MAAM,EAAuB,EAAS,GAAI,CAAS,EAC5C,EAAU,SAAS,CAC5B,CAEA,eAAe,EAAuB,EAAiB,EAAqB,EAAoC,CAC9G,IAAM,EAAa,EAAc,EAAK,KAAK,EAAS,CAAW,EAAI,EAC7D,EAAU,MAAM,EAAG,SAAS,QAAQ,EAAY,CAAE,cAAe,EAAK,CAAC,EAC7E,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAoB,EAAc,EAAK,KAAK,EAAa,EAAM,IAAI,EAAI,EAAM,KACnF,GAAI,EAAM,YAAY,EAAG,CACvB,MAAM,EAAuB,EAAS,EAAmB,CAAS,EAClE,QACF,CACI,EAAM,OAAO,GACf,EAAU,KAAK,CAAiB,CAEpC,CACF,CAEA,SAAgB,EAAgB,EAA4C,CAC1E,GAAI,CAAC,EAAG,WAAW,CAAQ,EACzB,OAIF,IAAM,EAAkB,KAAK,MAAM,EAAG,aAAa,EAAU,MAAM,CAAC,EAC9D,EAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAM,EACpD,GAAI,CAAC,EAAO,QAAS,OACrB,IAAM,EAAmB,EAAE,OAAO,CAChC,KAAM,EAAE,OAAO,EACf,KAAM,EAAE,OAAO,EACf,SAAU,EAAE,QAAQ,QAAQ,CAAC,CAAC,SAAS,CACzC,CAAC,EACD,OAAO,EAAO,KAAK,QAAS,GAAU,CACpC,IAAM,EAAS,EAAiB,UAAU,CAAK,EAC/C,OAAO,EAAO,QAAU,CAAC,EAAO,IAAI,EAAI,CAAC,CAC3C,CAAC,CACH,CAEA,SAAgB,EAAe,EAAuB,CACpD,OAAO,EAAM,MAAM,EAAG,CAAiB,CACzC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:http");r=e.__toESM(r,1);function i(e){let i=r.default.createServer((r
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:http");r=e.__toESM(r,1);function i(e){let t=o(e);return t.listen(),s(t)}async function a(e){let t=o(e);return await new Promise((e,n)=>{t.once(`error`,n),t.listen(0,`127.0.0.1`,()=>{t.off(`error`,n),e()})}),s(t)}function o(e){let i=n.default.resolve(e);return r.default.createServer((e,r)=>{let a;try{let t=new URL(e.url??`/`,`http://127.0.0.1`).pathname;a=decodeURIComponent(t.replaceAll(/%(?![\da-f]{2})/giu,`%25`))}catch{r.writeHead(400),r.end();return}a===`/`&&(a=`/index.html`);let o=a.endsWith(`/`)?n.default.join(a,`index.html`):a,s=n.default.resolve(i,`.${o}`),c=n.default.relative(i,s);if(c===`..`||c.startsWith(`..${n.default.sep}`)){r.writeHead(403),r.end();return}if(t.default.existsSync(s))try{let e=t.default.readFileSync(s);r.writeHead(200,{"Access-Control-Allow-Origin":`*`,Pragma:`no-cache`,"Cache-Control":`no-cache`,"Content-Type":l(o)}),r.end(e)}catch{r.statusCode=500,r.end()}else r.statusCode=404,r.end()})}function s(e){let t=e.address();if(!t)throw Error(`server has been unexpectedly closed`);return{[Symbol.asyncDispose]:async()=>{e.closeAllConnections(),await new Promise(t=>{e.close(()=>{t()})})},url:typeof t==`string`?t:`http://127.0.0.1:${t.port}`,port:typeof t==`object`?t.port:void 0}}const c={".css":`text/css`,".html":`text/html`,".js":`text/javascript`,".gif":`image/gif`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`};function l(e){for(let[t,n]of Object.entries(c))if(e.endsWith(t))return n;return`text/plain`}exports.startHttpServer=i,exports.startLocalHttpServer=a;
|
|
2
2
|
//# sourceMappingURL=startHttpServer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startHttpServer.cjs","names":["
|
|
1
|
+
{"version":3,"file":"startHttpServer.cjs","names":["path","http","fs"],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nexport interface HttpServer {\n [Symbol.asyncDispose](): Promise<void>;\n url: string;\n port: number | undefined;\n}\n\n/** Starts a web-page test server and returns its address synchronously. */\nexport function startHttpServer(dir: string): HttpServer {\n const server = createAssetServer(dir);\n server.listen();\n return createServerHandle(server);\n}\n\n/** Starts an asset server reachable only from the local machine. */\nexport async function startLocalHttpServer(dir: string): Promise<HttpServer> {\n const server = createAssetServer(dir);\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => {\n server.off('error', reject);\n resolve();\n });\n });\n return createServerHandle(server);\n}\n\nfunction createAssetServer(dir: string): http.Server {\n const rootPath = path.resolve(dir);\n return http.createServer((request, response) => {\n let pathname: string;\n try {\n const encodedPathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;\n pathname = decodeURIComponent(encodedPathname.replaceAll(/%(?![\\da-f]{2})/giu, '%25'));\n } catch {\n response.writeHead(400);\n response.end();\n return;\n }\n\n if (pathname === '/') {\n pathname = '/index.html';\n }\n\n const pathnameWithIndexHtml = pathname.endsWith('/') ? path.join(pathname, 'index.html') : pathname;\n\n const filePath = path.resolve(rootPath, `.${pathnameWithIndexHtml}`);\n const relativePath = path.relative(rootPath, filePath);\n if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`)) {\n response.writeHead(403);\n response.end();\n return;\n }\n\n if (fs.existsSync(filePath)) {\n try {\n const buffer = fs.readFileSync(filePath);\n response.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n Pragma: 'no-cache',\n 'Cache-Control': 'no-cache',\n 'Content-Type': getContentType(pathnameWithIndexHtml),\n });\n response.end(buffer);\n } catch {\n response.statusCode = 500;\n response.end();\n }\n } else {\n response.statusCode = 404;\n response.end();\n }\n });\n}\n\nfunction createServerHandle(server: http.Server): HttpServer {\n const address = server.address();\n if (!address) throw new Error('server has been unexpectedly closed');\n\n return {\n [Symbol.asyncDispose]: async () => {\n server.closeAllConnections();\n await new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n });\n },\n url: typeof address === 'string' ? address : `http://127.0.0.1:${address.port}`,\n port: typeof address === 'object' ? address.port : undefined,\n };\n}\n\nconst CONTENT_TYPE_BY_SUFFIX: Record<string, string> = {\n '.css': 'text/css',\n '.html': 'text/html',\n '.js': 'text/javascript',\n '.gif': 'image/gif',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n};\n\nfunction getContentType(pathname: string): string {\n for (const [suffix, contentType] of Object.entries(CONTENT_TYPE_BY_SUFFIX)) {\n if (pathname.endsWith(suffix)) return contentType;\n }\n return 'text/plain';\n}\n"],"mappings":"oMAWA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAAS,EAAkB,CAAG,EAEpC,OADA,EAAO,OAAO,EACP,EAAmB,CAAM,CAClC,CAGA,eAAsB,EAAqB,EAAkC,CAC3E,IAAM,EAAS,EAAkB,CAAG,EAQpC,OAPA,MAAM,IAAI,SAAe,EAAS,IAAW,CAC3C,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,EAAG,gBAAmB,CAClC,EAAO,IAAI,QAAS,CAAM,EAC1B,EAAQ,CACV,CAAC,CACH,CAAC,EACM,EAAmB,CAAM,CAClC,CAEA,SAAS,EAAkB,EAA0B,CACnD,IAAM,EAAWA,EAAAA,QAAK,QAAQ,CAAG,EACjC,OAAOC,EAAAA,QAAK,cAAc,EAAS,IAAa,CAC9C,IAAI,EACJ,GAAI,CACF,IAAM,EAAkB,IAAI,IAAI,EAAQ,KAAO,IAAK,kBAAkB,CAAC,CAAC,SACxE,EAAW,mBAAmB,EAAgB,WAAW,qBAAsB,KAAK,CAAC,CACvF,MAAQ,CACN,EAAS,UAAU,GAAG,EACtB,EAAS,IAAI,EACb,MACF,CAEI,IAAa,MACf,EAAW,eAGb,IAAM,EAAwB,EAAS,SAAS,GAAG,EAAID,EAAAA,QAAK,KAAK,EAAU,YAAY,EAAI,EAErF,EAAWA,EAAAA,QAAK,QAAQ,EAAU,IAAI,GAAuB,EAC7D,EAAeA,EAAAA,QAAK,SAAS,EAAU,CAAQ,EACrD,GAAI,IAAiB,MAAQ,EAAa,WAAW,KAAKA,EAAAA,QAAK,KAAK,EAAG,CACrE,EAAS,UAAU,GAAG,EACtB,EAAS,IAAI,EACb,MACF,CAEA,GAAIE,EAAAA,QAAG,WAAW,CAAQ,EACxB,GAAI,CACF,IAAM,EAASA,EAAAA,QAAG,aAAa,CAAQ,EACvC,EAAS,UAAU,IAAK,CACtB,8BAA+B,IAC/B,OAAQ,WACR,gBAAiB,WACjB,eAAgB,EAAe,CAAqB,CACtD,CAAC,EACD,EAAS,IAAI,CAAM,CACrB,MAAQ,CACN,EAAS,WAAa,IACtB,EAAS,IAAI,CACf,KAEA,GAAS,WAAa,IACtB,EAAS,IAAI,CAEjB,CAAC,CACH,CAEA,SAAS,EAAmB,EAAiC,CAC3D,IAAM,EAAU,EAAO,QAAQ,EAC/B,GAAI,CAAC,EAAS,MAAU,MAAM,qCAAqC,EAEnE,MAAO,EACJ,OAAO,cAAe,SAAY,CACjC,EAAO,oBAAoB,EAC3B,MAAM,IAAI,QAAe,GAAY,CACnC,EAAO,UAAY,CACjB,EAAQ,CACV,CAAC,CACH,CAAC,CACH,EACA,IAAK,OAAO,GAAY,SAAW,EAAU,oBAAoB,EAAQ,OACzE,KAAM,OAAO,GAAY,SAAW,EAAQ,KAAO,IAAA,EACrD,CACF,CAEA,MAAM,EAAiD,CACrD,OAAQ,WACR,QAAS,YACT,MAAO,kBACP,OAAQ,YACR,OAAQ,aACR,OAAQ,YACR,OAAQ,eACV,EAEA,SAAS,EAAe,EAA0B,CAChD,IAAK,GAAM,CAAC,EAAQ,KAAgB,OAAO,QAAQ,CAAsB,EACvE,GAAI,EAAS,SAAS,CAAM,EAAG,OAAO,EAExC,MAAO,YACT"}
|
|
@@ -3,7 +3,7 @@ export interface HttpServer {
|
|
|
3
3
|
url: string;
|
|
4
4
|
port: number | undefined;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Start a HTTP server for testing web pages.
|
|
8
|
-
*/
|
|
6
|
+
/** Starts a web-page test server and returns its address synchronously. */
|
|
9
7
|
export declare function startHttpServer(dir: string): HttpServer;
|
|
8
|
+
/** Starts an asset server reachable only from the local machine. */
|
|
9
|
+
export declare function startLocalHttpServer(dir: string): Promise<HttpServer>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:path";import n from"node:http";function r(r){let i=n.createServer((n,
|
|
1
|
+
import e from"node:fs";import t from"node:path";import n from"node:http";function r(e){let t=a(e);return t.listen(),o(t)}async function i(e){let t=a(e);return await new Promise((e,n)=>{t.once(`error`,n),t.listen(0,`127.0.0.1`,()=>{t.off(`error`,n),e()})}),o(t)}function a(r){let i=t.resolve(r);return n.createServer((n,r)=>{let a;try{let e=new URL(n.url??`/`,`http://127.0.0.1`).pathname;a=decodeURIComponent(e.replaceAll(/%(?![\da-f]{2})/giu,`%25`))}catch{r.writeHead(400),r.end();return}a===`/`&&(a=`/index.html`);let o=a.endsWith(`/`)?t.join(a,`index.html`):a,s=t.resolve(i,`.${o}`),l=t.relative(i,s);if(l===`..`||l.startsWith(`..${t.sep}`)){r.writeHead(403),r.end();return}if(e.existsSync(s))try{let t=e.readFileSync(s);r.writeHead(200,{"Access-Control-Allow-Origin":`*`,Pragma:`no-cache`,"Cache-Control":`no-cache`,"Content-Type":c(o)}),r.end(t)}catch{r.statusCode=500,r.end()}else r.statusCode=404,r.end()})}function o(e){let t=e.address();if(!t)throw Error(`server has been unexpectedly closed`);return{[Symbol.asyncDispose]:async()=>{e.closeAllConnections(),await new Promise(t=>{e.close(()=>{t()})})},url:typeof t==`string`?t:`http://127.0.0.1:${t.port}`,port:typeof t==`object`?t.port:void 0}}const s={".css":`text/css`,".html":`text/html`,".js":`text/javascript`,".gif":`image/gif`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`};function c(e){for(let[t,n]of Object.entries(s))if(e.endsWith(t))return n;return`text/plain`}export{r as startHttpServer,i as startLocalHttpServer};
|
|
2
2
|
//# sourceMappingURL=startHttpServer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startHttpServer.js","names":[],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nexport interface HttpServer {\n [Symbol.asyncDispose](): Promise<void>;\n url: string;\n port: number | undefined;\n}\n\n
|
|
1
|
+
{"version":3,"file":"startHttpServer.js","names":[],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nexport interface HttpServer {\n [Symbol.asyncDispose](): Promise<void>;\n url: string;\n port: number | undefined;\n}\n\n/** Starts a web-page test server and returns its address synchronously. */\nexport function startHttpServer(dir: string): HttpServer {\n const server = createAssetServer(dir);\n server.listen();\n return createServerHandle(server);\n}\n\n/** Starts an asset server reachable only from the local machine. */\nexport async function startLocalHttpServer(dir: string): Promise<HttpServer> {\n const server = createAssetServer(dir);\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => {\n server.off('error', reject);\n resolve();\n });\n });\n return createServerHandle(server);\n}\n\nfunction createAssetServer(dir: string): http.Server {\n const rootPath = path.resolve(dir);\n return http.createServer((request, response) => {\n let pathname: string;\n try {\n const encodedPathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;\n pathname = decodeURIComponent(encodedPathname.replaceAll(/%(?![\\da-f]{2})/giu, '%25'));\n } catch {\n response.writeHead(400);\n response.end();\n return;\n }\n\n if (pathname === '/') {\n pathname = '/index.html';\n }\n\n const pathnameWithIndexHtml = pathname.endsWith('/') ? path.join(pathname, 'index.html') : pathname;\n\n const filePath = path.resolve(rootPath, `.${pathnameWithIndexHtml}`);\n const relativePath = path.relative(rootPath, filePath);\n if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`)) {\n response.writeHead(403);\n response.end();\n return;\n }\n\n if (fs.existsSync(filePath)) {\n try {\n const buffer = fs.readFileSync(filePath);\n response.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n Pragma: 'no-cache',\n 'Cache-Control': 'no-cache',\n 'Content-Type': getContentType(pathnameWithIndexHtml),\n });\n response.end(buffer);\n } catch {\n response.statusCode = 500;\n response.end();\n }\n } else {\n response.statusCode = 404;\n response.end();\n }\n });\n}\n\nfunction createServerHandle(server: http.Server): HttpServer {\n const address = server.address();\n if (!address) throw new Error('server has been unexpectedly closed');\n\n return {\n [Symbol.asyncDispose]: async () => {\n server.closeAllConnections();\n await new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n });\n },\n url: typeof address === 'string' ? address : `http://127.0.0.1:${address.port}`,\n port: typeof address === 'object' ? address.port : undefined,\n };\n}\n\nconst CONTENT_TYPE_BY_SUFFIX: Record<string, string> = {\n '.css': 'text/css',\n '.html': 'text/html',\n '.js': 'text/javascript',\n '.gif': 'image/gif',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n};\n\nfunction getContentType(pathname: string): string {\n for (const [suffix, contentType] of Object.entries(CONTENT_TYPE_BY_SUFFIX)) {\n if (pathname.endsWith(suffix)) return contentType;\n }\n return 'text/plain';\n}\n"],"mappings":"yEAWA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAAS,EAAkB,CAAG,EAEpC,OADA,EAAO,OAAO,EACP,EAAmB,CAAM,CAClC,CAGA,eAAsB,EAAqB,EAAkC,CAC3E,IAAM,EAAS,EAAkB,CAAG,EAQpC,OAPA,MAAM,IAAI,SAAe,EAAS,IAAW,CAC3C,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,EAAG,gBAAmB,CAClC,EAAO,IAAI,QAAS,CAAM,EAC1B,EAAQ,CACV,CAAC,CACH,CAAC,EACM,EAAmB,CAAM,CAClC,CAEA,SAAS,EAAkB,EAA0B,CACnD,IAAM,EAAW,EAAK,QAAQ,CAAG,EACjC,OAAO,EAAK,cAAc,EAAS,IAAa,CAC9C,IAAI,EACJ,GAAI,CACF,IAAM,EAAkB,IAAI,IAAI,EAAQ,KAAO,IAAK,kBAAkB,CAAC,CAAC,SACxE,EAAW,mBAAmB,EAAgB,WAAW,qBAAsB,KAAK,CAAC,CACvF,MAAQ,CACN,EAAS,UAAU,GAAG,EACtB,EAAS,IAAI,EACb,MACF,CAEI,IAAa,MACf,EAAW,eAGb,IAAM,EAAwB,EAAS,SAAS,GAAG,EAAI,EAAK,KAAK,EAAU,YAAY,EAAI,EAErF,EAAW,EAAK,QAAQ,EAAU,IAAI,GAAuB,EAC7D,EAAe,EAAK,SAAS,EAAU,CAAQ,EACrD,GAAI,IAAiB,MAAQ,EAAa,WAAW,KAAK,EAAK,KAAK,EAAG,CACrE,EAAS,UAAU,GAAG,EACtB,EAAS,IAAI,EACb,MACF,CAEA,GAAI,EAAG,WAAW,CAAQ,EACxB,GAAI,CACF,IAAM,EAAS,EAAG,aAAa,CAAQ,EACvC,EAAS,UAAU,IAAK,CACtB,8BAA+B,IAC/B,OAAQ,WACR,gBAAiB,WACjB,eAAgB,EAAe,CAAqB,CACtD,CAAC,EACD,EAAS,IAAI,CAAM,CACrB,MAAQ,CACN,EAAS,WAAa,IACtB,EAAS,IAAI,CACf,KAEA,GAAS,WAAa,IACtB,EAAS,IAAI,CAEjB,CAAC,CACH,CAEA,SAAS,EAAmB,EAAiC,CAC3D,IAAM,EAAU,EAAO,QAAQ,EAC/B,GAAI,CAAC,EAAS,MAAU,MAAM,qCAAqC,EAEnE,MAAO,EACJ,OAAO,cAAe,SAAY,CACjC,EAAO,oBAAoB,EAC3B,MAAM,IAAI,QAAe,GAAY,CACnC,EAAO,UAAY,CACjB,EAAQ,CACV,CAAC,CACH,CAAC,CACH,EACA,IAAK,OAAO,GAAY,SAAW,EAAU,oBAAoB,EAAQ,OACzE,KAAM,OAAO,GAAY,SAAW,EAAQ,KAAO,IAAA,EACrD,CACF,CAEA,MAAM,EAAiD,CACrD,OAAQ,WACR,QAAS,YACT,MAAO,kBACP,OAAQ,YACR,OAAQ,aACR,OAAQ,YACR,OAAQ,eACV,EAEA,SAAS,EAAe,EAA0B,CAChD,IAAK,GAAM,CAAC,EAAQ,KAAgB,OAAO,QAAQ,CAAsB,EACvE,GAAI,EAAS,SAAS,CAAM,EAAG,OAAO,EAExC,MAAO,YACT"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/compareStdoutAsSpaceSeparatedTokens.cjs"),t=require("./types/testCaseResult.cjs"),n=require("./helpers/printTestCaseResult.cjs"),r=require("./helpers/compareExpectedOutputFiles.cjs"),i=require("./helpers/defaultStdioHarness.cjs"),a=require("./helpers/parseArgs.cjs"),o=require("./helpers/parseCsv.cjs"),s=require("./helpers/readTestCases.cjs"),c=require("./helpers/removeCommentsInSourceCode.cjs"),l=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),u=require("./helpers/runPythonInTemporaryUvProject.cjs"),d=require("./helpers/sourceCodeGrammars.cjs"),f=require("./helpers/startHttpServer.cjs"),p=require("./types/problem.cjs"),m=require("./learningMaterial/frontmatter.cjs"),h=require("./learningMaterial/validationResult.cjs"),g=require("./learningMaterial/validateMaterial.cjs"),_=require("./learningMaterial/validateContest.cjs"),v=require("./learningMaterial/validateCourse.cjs"),y=require("./learningMaterial/validateProblem.cjs"),b=require("./types/decisionCode.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=b.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_REPORTED_FILE_BYTES=r.MAX_REPORTED_FILE_BYTES,exports.TEST_CASE_RESULT_PREFIX=t.TEST_CASE_RESULT_PREFIX,exports.codeRuleSchema=p.codeRuleSchema,exports.compareExpectedOutputFiles=r.compareExpectedOutputFiles,exports.compareStdoutAsSpaceSeparatedTokens=e.compareStdoutAsSpaceSeparatedTokens,exports.copyPackageManagerProjectFiles=l.copyPackageManagerProjectFiles,exports.encodeFileForTestCaseResult=n.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=i.findDefaultStdioHarnessFiles,exports.formatValidationReport=h.formatValidationReport,exports.formatZodIssues=h.formatZodIssues,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.mergeSubmissionPeriods=g.mergeSubmissionPeriods,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.parseFrontmatter=m.parseFrontmatter,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.reportDuplicateIds=h.reportDuplicateIds,exports.reportExcessChatMarkers=g.reportExcessChatMarkers,exports.reportForeignProblemCourseIds=g.reportForeignProblemCourseIds,exports.runCommandInTemporaryPackageManagerProject=l.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=u.runPythonInTemporaryUvProject,exports.startHttpServer=f.startHttpServer,exports.testCaseResultSchema=t.testCaseResultSchema,exports.validateContestFile=_.validateContestFile,exports.validateCourseDirectory=v.validateCourseDirectory,exports.validateMaterialFile=g.validateMaterialFile,exports.validateProblemDirectory=y.validateProblemDirectory,exports.validateSubmissionPeriods=g.validateSubmissionPeriods;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/compareStdoutAsSpaceSeparatedTokens.cjs"),t=require("./types/testCaseResult.cjs"),n=require("./helpers/printTestCaseResult.cjs"),r=require("./helpers/compareExpectedOutputFiles.cjs"),i=require("./helpers/defaultStdioHarness.cjs"),a=require("./helpers/parseArgs.cjs"),o=require("./helpers/parseCsv.cjs"),s=require("./helpers/readTestCases.cjs"),c=require("./helpers/removeCommentsInSourceCode.cjs"),l=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),u=require("./helpers/runPythonInTemporaryUvProject.cjs"),d=require("./helpers/sourceCodeGrammars.cjs"),f=require("./helpers/startHttpServer.cjs"),p=require("./types/problem.cjs"),m=require("./learningMaterial/frontmatter.cjs"),h=require("./learningMaterial/validationResult.cjs"),g=require("./learningMaterial/validateMaterial.cjs"),_=require("./learningMaterial/validateContest.cjs"),v=require("./learningMaterial/validateCourse.cjs"),y=require("./learningMaterial/validateProblem.cjs"),b=require("./types/decisionCode.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=b.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_REPORTED_FILE_BYTES=r.MAX_REPORTED_FILE_BYTES,exports.TEST_CASE_RESULT_PREFIX=t.TEST_CASE_RESULT_PREFIX,exports.codeRuleSchema=p.codeRuleSchema,exports.compareExpectedOutputFiles=r.compareExpectedOutputFiles,exports.compareStdoutAsSpaceSeparatedTokens=e.compareStdoutAsSpaceSeparatedTokens,exports.copyPackageManagerProjectFiles=l.copyPackageManagerProjectFiles,exports.encodeFileForTestCaseResult=n.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=i.findDefaultStdioHarnessFiles,exports.formatValidationReport=h.formatValidationReport,exports.formatZodIssues=h.formatZodIssues,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.mergeSubmissionPeriods=g.mergeSubmissionPeriods,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.parseFrontmatter=m.parseFrontmatter,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.reportDuplicateIds=h.reportDuplicateIds,exports.reportExcessChatMarkers=g.reportExcessChatMarkers,exports.reportForeignProblemCourseIds=g.reportForeignProblemCourseIds,exports.runCommandInTemporaryPackageManagerProject=l.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=u.runPythonInTemporaryUvProject,exports.startHttpServer=f.startHttpServer,exports.startLocalHttpServer=f.startLocalHttpServer,exports.testCaseResultSchema=t.testCaseResultSchema,exports.validateContestFile=_.validateContestFile,exports.validateCourseDirectory=v.validateCourseDirectory,exports.validateMaterialFile=g.validateMaterialFile,exports.validateProblemDirectory=y.validateProblemDirectory,exports.validateSubmissionPeriods=g.validateSubmissionPeriods;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{compareStdoutAsSpaceSeparatedTokens as e}from"./helpers/compareStdoutAsSpaceSeparatedTokens.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as r,printTestCaseResult as i}from"./helpers/printTestCaseResult.js";import{MAX_COMPARED_FILE_BYTES as a,MAX_REPORTED_FILE_BYTES as o,compareExpectedOutputFiles as s,judgeAgainstExpectations as c,mergeComparisonOutputFiles as l}from"./helpers/compareExpectedOutputFiles.js";import{HARNESS_FILE_PRESETS as u,findDefaultStdioHarnessFiles as d,isDefaultStdioHarnessSource as f}from"./helpers/defaultStdioHarness.js";import{parseArgs as p}from"./helpers/parseArgs.js";import{CsvParseError as m,parseCsv as h,parseCsvRecords as g}from"./helpers/parseCsv.js";import{readTestCases as _}from"./helpers/readTestCases.js";import{removeCommentsAndStringsInSourceCode as v,removeCommentsInSourceCode as y}from"./helpers/removeCommentsInSourceCode.js";import{copyPackageManagerProjectFiles as b,runCommandInTemporaryPackageManagerProject as x}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as S}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as C}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as w}from"./helpers/startHttpServer.js";import{codeRuleSchema as
|
|
1
|
+
import{compareStdoutAsSpaceSeparatedTokens as e}from"./helpers/compareStdoutAsSpaceSeparatedTokens.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as r,printTestCaseResult as i}from"./helpers/printTestCaseResult.js";import{MAX_COMPARED_FILE_BYTES as a,MAX_REPORTED_FILE_BYTES as o,compareExpectedOutputFiles as s,judgeAgainstExpectations as c,mergeComparisonOutputFiles as l}from"./helpers/compareExpectedOutputFiles.js";import{HARNESS_FILE_PRESETS as u,findDefaultStdioHarnessFiles as d,isDefaultStdioHarnessSource as f}from"./helpers/defaultStdioHarness.js";import{parseArgs as p}from"./helpers/parseArgs.js";import{CsvParseError as m,parseCsv as h,parseCsvRecords as g}from"./helpers/parseCsv.js";import{readTestCases as _}from"./helpers/readTestCases.js";import{removeCommentsAndStringsInSourceCode as v,removeCommentsInSourceCode as y}from"./helpers/removeCommentsInSourceCode.js";import{copyPackageManagerProjectFiles as b,runCommandInTemporaryPackageManagerProject as x}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as S}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as C}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as w,startLocalHttpServer as T}from"./helpers/startHttpServer.js";import{codeRuleSchema as E,normalizeCodeRule as D,problemMarkdownFrontMatterSchema as O}from"./types/problem.js";import{parseFrontmatter as k}from"./learningMaterial/frontmatter.js";import{formatValidationReport as A,formatZodIssues as j,reportDuplicateIds as M}from"./learningMaterial/validationResult.js";import{mergeSubmissionPeriods as N,reportExcessChatMarkers as P,reportForeignProblemCourseIds as F,validateMaterialFile as I,validateSubmissionPeriods as L}from"./learningMaterial/validateMaterial.js";import{validateContestFile as R}from"./learningMaterial/validateContest.js";import{validateCourseDirectory as z}from"./learningMaterial/validateCourse.js";import{validateProblemDirectory as B}from"./learningMaterial/validateProblem.js";import{DecisionCode as V}from"./types/decisionCode.js";export{m as CsvParseError,V as DecisionCode,u as HARNESS_FILE_PRESETS,a as MAX_COMPARED_FILE_BYTES,o as MAX_REPORTED_FILE_BYTES,t as TEST_CASE_RESULT_PREFIX,E as codeRuleSchema,s as compareExpectedOutputFiles,e as compareStdoutAsSpaceSeparatedTokens,b as copyPackageManagerProjectFiles,r as encodeFileForTestCaseResult,d as findDefaultStdioHarnessFiles,A as formatValidationReport,j as formatZodIssues,f as isDefaultStdioHarnessSource,c as judgeAgainstExpectations,C as languageIdToSourceCodeGrammar,l as mergeComparisonOutputFiles,N as mergeSubmissionPeriods,D as normalizeCodeRule,p as parseArgs,h as parseCsv,g as parseCsvRecords,k as parseFrontmatter,i as printTestCaseResult,O as problemMarkdownFrontMatterSchema,_ as readTestCases,v as removeCommentsAndStringsInSourceCode,y as removeCommentsInSourceCode,M as reportDuplicateIds,P as reportExcessChatMarkers,F as reportForeignProblemCourseIds,x as runCommandInTemporaryPackageManagerProject,S as runPythonInTemporaryUvProject,w as startHttpServer,T as startLocalHttpServer,n as testCaseResultSchema,R as validateContestFile,z as validateCourseDirectory,I as validateMaterialFile,B as validateProblemDirectory,L as validateSubmissionPeriods};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/printTestCaseResult.cjs"),n=require("../helpers/parseArgs.cjs"),r=require("../types/decisionCode.cjs"),i=require("../helpers/javaJudge.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("node:os"),c=require("node:child_process"),l=require("node:timers/promises");const u=`judge`,d=59e3,f=`http://localhost:${d}`,p=6e4;async function m(e){if(process.argv.includes(`--evaluate`)){try{await e.evaluate()}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}return}let{cwd:i}=n.parseArgs(process.argv);if(!i)throw Error(`cwd argument required`);let c=`test1`,l=await a.default.promises.mkdtemp(o.default.join((0,s.tmpdir)(),`spring_judge_`)),d=o.default.join(l,`build`),f;try{await h(d,i,e.problemDirectoryPath);let n=v(d);if(n){t.printTestCaseResult({testCaseId:c,...n});return}let r=await b(d,o.default.join(d,`target`,`${u}.jar`));if(f=r.childProcess,r.failure){t.printTestCaseResult({testCaseId:c,...r.failure});return}let a=C(d,e.problemDirectoryPath);t.printTestCaseResult({testCaseId:c,...a})}catch(e){t.printTestCaseResult({testCaseId:c,decisionCode:r.DecisionCode.JUDGE_NOT_AVAILABLE,stderr:e instanceof Error?e.message:String(e)})}finally{await S(f),await a.default.promises.rm(l,{recursive:!0,force:!0})}}async function h(e,t,n){await a.default.promises.mkdir(o.default.join(e,`src/main/java`),{recursive:!0}),await a.default.promises.mkdir(o.default.join(e,`src/main/resources`),{recursive:!0}),await a.default.promises.copyFile(o.default.join(n,`pom.xml`),o.default.join(e,`pom.xml`)),await a.default.promises.writeFile(o.default.join(e,`src/main/resources/application.properties`),g());for(let n of await i.listSubmissionFiles(t)){let r=o.default.join(t,n),i=await _(e,n,r);await a.default.promises.mkdir(o.default.dirname(i),{recursive:!0}),await a.default.promises.copyFile(r,i)}}function g(){return`spring.application.name=${u}
|
|
2
|
+
server.port=${d}
|
|
3
|
+
spring.main.banner-mode=off
|
|
4
|
+
logging.level.org.springframework=ERROR
|
|
5
|
+
logging.level.org.apache=ERROR
|
|
6
|
+
spring.main.log-startup-info=false
|
|
7
|
+
`}async function _(e,t,n){let r=t.replaceAll(`\\`,`/`);return r.startsWith(`src/main/`)?o.default.join(e,r):o.default.extname(t).toLowerCase()===`.java`?o.default.join(e,`src/main/java`,await i.resolveJavaRelativePath(n,t)):o.default.join(e,`src/main/resources`,r)}function v(e){let t=Date.now(),n=(0,c.spawnSync)(`mvn`,[`clean`,`package`,`--quiet`,`--batch-mode`,`--offline`,`-DskipTests`],{cwd:e,encoding:`utf8`,env:{...process.env,CI:``,FORCE_COLOR:`0`},timeout:9e4,maxBuffer:i.MAX_OUTPUT_LENGTH*4}),a=(Date.now()-t)/1e3,o=i.truncateOutput(n.stdout??``),s=i.truncateOutput(n.stderr??``);if(w(n.error))return{decisionCode:r.DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,stderr:s,stdout:o,timeSeconds:a};if((n.status??0)!==0)return{decisionCode:r.DecisionCode.BUILD_ERROR,exitStatus:n.status??void 0,stderr:i.truncateOutput(y(o)||s),stdout:o,timeSeconds:a}}function y(e){let t=e.replaceAll(/\[\d*(?:;\d+)*m/gu,``),n=new Set;for(let e of t.matchAll(/\].*?\[\d+,\d+\]\s+(.*?)$/gmu)){let t=e[1].trim();t&&n.add(t)}return n.size>0?`ビルドエラー:\n${[...n].join(`
|
|
8
|
+
`)}`:``}async function b(e,t){let n=Date.now(),a=(0,c.spawn)(`java`,[`-jar`,t],{cwd:e,env:{...process.env,SPRING_MAIN_WEB_APPLICATION_TYPE:`servlet`},stdio:[`ignore`,`pipe`,`pipe`]}),o=``,s=``;a.stdout?.on(`data`,e=>{o=i.truncateOutput(o+e.toString(`utf8`))}),a.stderr?.on(`data`,e=>{s=i.truncateOutput(s+e.toString(`utf8`))});let u=!1;for(a.on(`exit`,()=>{u=!0});Date.now()-n<p;){if(await x())return{childProcess:a};if(u)break;await(0,l.setTimeout)(200)}let d=(Date.now()-n)/1e3,f=u?`アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。`:`アプリケーションが ${p/1e3} 秒以内に起動しませんでした。`;return{childProcess:a,failure:{decisionCode:u?r.DecisionCode.RUNTIME_ERROR:r.DecisionCode.TIME_LIMIT_EXCEEDED,exitStatus:a.exitCode??void 0,feedbackMarkdown:f,stderr:s||void 0,stdout:o||void 0,timeSeconds:d}}}async function x(){try{let e=await fetch(f,{signal:AbortSignal.timeout(1e3)});return await e.body?.cancel(),e.ok||e.status===404}catch{return!1}}async function S(e){if(!e||e.exitCode!==null)return;e.kill(`SIGTERM`);let t=Date.now();for(;Date.now()-t<5e3&&e.exitCode===null;)await(0,l.setTimeout)(100);e.exitCode===null&&e.kill(`SIGKILL`)}function C(e,t){let n=Date.now(),a=(0,c.spawnSync)(`bun`,[`run`,`./judge.ts`,`--evaluate`],{cwd:t,encoding:`utf8`,env:{...process.env,WORKING_DIRECTORY_PATH:e},timeout:6e4,maxBuffer:i.MAX_OUTPUT_LENGTH*4}),s=(Date.now()-n)/1e3,l=i.truncateOutput(a.stdout??``),u=i.truncateOutput(a.stderr??``),d=i.readOutputFiles(o.default.join(e,`__SCREENSHOTS.json`));return w(a.error)?{decisionCode:r.DecisionCode.TIME_LIMIT_EXCEEDED,stderr:u,stdout:l,timeSeconds:s,outputFiles:d}:(a.status??0)===0?{decisionCode:r.DecisionCode.ACCEPTED,exitStatus:a.status??void 0,stderr:u||void 0,stdout:l||void 0,timeSeconds:s,outputFiles:d}:{decisionCode:r.DecisionCode.WRONG_ANSWER,exitStatus:a.status??void 0,stderr:u,stdout:l,timeSeconds:s,outputFiles:d}}function w(e){return!!e&&`code`in e&&e.code===`ETIMEDOUT`}function T(e){return`${f}${e.startsWith(`/`)?e:`/${e}`}`}exports.buildSpringBootUrl=T,exports.springBootJudgePreset=m;
|
|
9
|
+
//# sourceMappingURL=springBoot.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"springBoot.cjs","names":["parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","spawnSync","MAX_OUTPUT_LENGTH","truncateOutput","spawn","sleep","readOutputFiles"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (isTimeoutError(result.error)) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (isTimeoutError(result.error)) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nfunction isTimeoutError(error: Error | undefined): boolean {\n return !!error && 'code' in error && error.code === 'ETIMEDOUT';\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface SpringBootJudgePresetOptions {
|
|
2
|
+
problemDirectoryPath: string;
|
|
3
|
+
evaluate: () => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void>;
|
|
6
|
+
export declare function buildSpringBootUrl(urlPath: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{printTestCaseResult as e}from"../helpers/printTestCaseResult.js";import{parseArgs as t}from"../helpers/parseArgs.js";import{DecisionCode as n}from"../types/decisionCode.js";import{MAX_OUTPUT_LENGTH as r,listSubmissionFiles as i,readOutputFiles as a,resolveJavaRelativePath as o,truncateOutput as s}from"../helpers/javaJudge.js";import c from"node:fs";import l from"node:path";import{tmpdir as u}from"node:os";import{spawn as d,spawnSync as f}from"node:child_process";import{setTimeout as p}from"node:timers/promises";const m=`judge`,h=59e3,g=`http://localhost:${h}`,_=6e4;async function v(r){if(process.argv.includes(`--evaluate`)){try{await r.evaluate()}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}return}let{cwd:i}=t(process.argv);if(!i)throw Error(`cwd argument required`);let a=`test1`,o=await c.promises.mkdtemp(l.join(u(),`spring_judge_`)),s=l.join(o,`build`),d;try{await y(s,i,r.problemDirectoryPath);let t=S(s);if(t){e({testCaseId:a,...t});return}let n=await w(s,l.join(s,`target`,`${m}.jar`));if(d=n.childProcess,n.failure){e({testCaseId:a,...n.failure});return}let o=D(s,r.problemDirectoryPath);e({testCaseId:a,...o})}catch(t){e({testCaseId:a,decisionCode:n.JUDGE_NOT_AVAILABLE,stderr:t instanceof Error?t.message:String(t)})}finally{await E(d),await c.promises.rm(o,{recursive:!0,force:!0})}}async function y(e,t,n){await c.promises.mkdir(l.join(e,`src/main/java`),{recursive:!0}),await c.promises.mkdir(l.join(e,`src/main/resources`),{recursive:!0}),await c.promises.copyFile(l.join(n,`pom.xml`),l.join(e,`pom.xml`)),await c.promises.writeFile(l.join(e,`src/main/resources/application.properties`),b());for(let n of await i(t)){let r=l.join(t,n),i=await x(e,n,r);await c.promises.mkdir(l.dirname(i),{recursive:!0}),await c.promises.copyFile(r,i)}}function b(){return`spring.application.name=${m}
|
|
2
|
+
server.port=${h}
|
|
3
|
+
spring.main.banner-mode=off
|
|
4
|
+
logging.level.org.springframework=ERROR
|
|
5
|
+
logging.level.org.apache=ERROR
|
|
6
|
+
spring.main.log-startup-info=false
|
|
7
|
+
`}async function x(e,t,n){let r=t.replaceAll(`\\`,`/`);return r.startsWith(`src/main/`)?l.join(e,r):l.extname(t).toLowerCase()===`.java`?l.join(e,`src/main/java`,await o(n,t)):l.join(e,`src/main/resources`,r)}function S(e){let t=Date.now(),i=f(`mvn`,[`clean`,`package`,`--quiet`,`--batch-mode`,`--offline`,`-DskipTests`],{cwd:e,encoding:`utf8`,env:{...process.env,CI:``,FORCE_COLOR:`0`},timeout:9e4,maxBuffer:r*4}),a=(Date.now()-t)/1e3,o=s(i.stdout??``),c=s(i.stderr??``);if(O(i.error))return{decisionCode:n.BUILD_TIME_LIMIT_EXCEEDED,stderr:c,stdout:o,timeSeconds:a};if((i.status??0)!==0)return{decisionCode:n.BUILD_ERROR,exitStatus:i.status??void 0,stderr:s(C(o)||c),stdout:o,timeSeconds:a}}function C(e){let t=e.replaceAll(/\[\d*(?:;\d+)*m/gu,``),n=new Set;for(let e of t.matchAll(/\].*?\[\d+,\d+\]\s+(.*?)$/gmu)){let t=e[1].trim();t&&n.add(t)}return n.size>0?`ビルドエラー:\n${[...n].join(`
|
|
8
|
+
`)}`:``}async function w(e,t){let r=Date.now(),i=d(`java`,[`-jar`,t],{cwd:e,env:{...process.env,SPRING_MAIN_WEB_APPLICATION_TYPE:`servlet`},stdio:[`ignore`,`pipe`,`pipe`]}),a=``,o=``;i.stdout?.on(`data`,e=>{a=s(a+e.toString(`utf8`))}),i.stderr?.on(`data`,e=>{o=s(o+e.toString(`utf8`))});let c=!1;for(i.on(`exit`,()=>{c=!0});Date.now()-r<_;){if(await T())return{childProcess:i};if(c)break;await p(200)}let l=(Date.now()-r)/1e3,u=c?`アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。`:`アプリケーションが ${_/1e3} 秒以内に起動しませんでした。`;return{childProcess:i,failure:{decisionCode:c?n.RUNTIME_ERROR:n.TIME_LIMIT_EXCEEDED,exitStatus:i.exitCode??void 0,feedbackMarkdown:u,stderr:o||void 0,stdout:a||void 0,timeSeconds:l}}}async function T(){try{let e=await fetch(g,{signal:AbortSignal.timeout(1e3)});return await e.body?.cancel(),e.ok||e.status===404}catch{return!1}}async function E(e){if(!e||e.exitCode!==null)return;e.kill(`SIGTERM`);let t=Date.now();for(;Date.now()-t<5e3&&e.exitCode===null;)await p(100);e.exitCode===null&&e.kill(`SIGKILL`)}function D(e,t){let i=Date.now(),o=f(`bun`,[`run`,`./judge.ts`,`--evaluate`],{cwd:t,encoding:`utf8`,env:{...process.env,WORKING_DIRECTORY_PATH:e},timeout:6e4,maxBuffer:r*4}),c=(Date.now()-i)/1e3,u=s(o.stdout??``),d=s(o.stderr??``),p=a(l.join(e,`__SCREENSHOTS.json`));return O(o.error)?{decisionCode:n.TIME_LIMIT_EXCEEDED,stderr:d,stdout:u,timeSeconds:c,outputFiles:p}:(o.status??0)===0?{decisionCode:n.ACCEPTED,exitStatus:o.status??void 0,stderr:d||void 0,stdout:u||void 0,timeSeconds:c,outputFiles:p}:{decisionCode:n.WRONG_ANSWER,exitStatus:o.status??void 0,stderr:d,stdout:u,timeSeconds:c,outputFiles:p}}function O(e){return!!e&&`code`in e&&e.code===`ETIMEDOUT`}function k(e){return`${g}${e.startsWith(`/`)?e:`/${e}`}`}export{k as buildSpringBootUrl,v as springBootJudgePreset};
|
|
9
|
+
//# sourceMappingURL=springBoot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"springBoot.js","names":["sleep"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (isTimeoutError(result.error)) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (isTimeoutError(result.error)) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nfunction isTimeoutError(error: Error | undefined): boolean {\n return !!error && 'code' in error && error.code === 'ETIMEDOUT';\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
package/dist/presets/tomcat.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/printTestCaseResult.cjs"),n=require("../helpers/parseArgs.cjs"),r=require("../types/decisionCode.cjs");let
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/printTestCaseResult.cjs"),n=require("../helpers/parseArgs.cjs"),r=require("../types/decisionCode.cjs"),i=require("../helpers/javaJudge.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("node:os"),c=require("node:child_process"),l=require("node:http");l=e.__toESM(l,1);let u=require("node:timers/promises");const d=`judge`,f=`http://localhost:59000`,p=new Set([`.java`,`.jsp`,`.html`,`.htm`,`.xml`,`.js`,`.css`]);async function m(e){if(process.argv.includes(`--evaluate`)){try{await e.evaluate()}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}return}let{cwd:i}=n.parseArgs(process.argv);if(!i)throw Error(`cwd argument required`);let c=`test1`,l=await _(i,e.forbiddenTexts??[]);if(l){t.printTestCaseResult({testCaseId:c,...l});return}let u=await a.default.promises.mkdtemp(o.default.join((0,s.tmpdir)(),`tomcat_judge_`)),d=o.default.join(u,`build`),f,p;try{await a.default.promises.mkdir(d,{recursive:!0}),await h(d,i,e);let n=v(d);if(n){t.printTestCaseResult({testCaseId:c,...n});return}p=O(),f=await b(p,u),await T(p,f),await x(d,f),await S(p,f);let r=C(d,e.problemDirectoryPath);t.printTestCaseResult({testCaseId:c,...r})}catch(e){t.printTestCaseResult({testCaseId:c,decisionCode:r.DecisionCode.JUDGE_NOT_AVAILABLE,stderr:e instanceof Error?e.message:String(e)})}finally{try{p&&f&&await T(p,f)}catch{}await a.default.promises.rm(u,{recursive:!0,force:!0})}}async function h(e,t,n){await a.default.promises.mkdir(o.default.join(e,`src/main/java`),{recursive:!0}),await a.default.promises.mkdir(o.default.join(e,`src/main/webapp/WEB-INF/jsp`),{recursive:!0}),await a.default.promises.copyFile(o.default.join(n.problemDirectoryPath,`pom.xml`),o.default.join(e,`pom.xml`));let r=await i.listSubmissionFiles(t);for(let i of r){let r=o.default.join(t,i),s=await g(e,i,r,n.jspDirectory);await a.default.promises.mkdir(o.default.dirname(s),{recursive:!0}),await a.default.promises.copyFile(r,s)}}async function g(e,t,n,r){let a=t.replaceAll(`\\`,`/`);if(a.startsWith(`src/main/`))return o.default.join(e,a);if(a.startsWith(`WEB-INF/`)||a.startsWith(`META-INF/`))return o.default.join(e,`src/main/webapp`,a);let s=o.default.extname(t).toLowerCase();return s===`.java`?o.default.join(e,`src/main/java`,await i.resolveJavaRelativePath(n,t)):s===`.jsp`?o.default.join(e,`src/main/webapp`,r,o.default.basename(t)):o.default.join(e,`src/main/webapp`,a)}async function _(e,t){let n=[];for(let r of await i.listSubmissionFiles(e)){if(!p.has(o.default.extname(r).toLowerCase()))continue;let i=await a.default.promises.readFile(o.default.join(e,r),`utf8`);for(let e of t)i.includes(e)&&n.push({path:r,pattern:e})}if(n.length!==0)return{decisionCode:r.DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
|
|
2
2
|
ソースコードを修正してから再度提出してください。
|
|
3
3
|
|
|
4
4
|
| ファイル | 禁止パターン |
|
|
5
5
|
| -------- | ------------ |
|
|
6
6
|
`+n.map(e=>`| \`${e.path}\` | \`${e.pattern}\` |`).join(`
|
|
7
|
-
`)}}
|
|
8
|
-
${t}`.trim())}async function
|
|
9
|
-
`),u=/(?:^|\n)(\d+(?:\.\d+)?) (\d+)\s*$/u.exec(l),d=u?l.slice(0,u.index):l,f=Number(u?.[1])||s,p=1024*Number(u?.[2]??0);return{status:o.status===124?0:o.status??void 0,stdout:o.stdout,stderr:d,timeSeconds:o.status===124?n+.001:f,memoryBytes:p}}async function
|
|
7
|
+
`)}}function v(e){let t=w([`mvn`,`clean`,`package`,`--quiet`,`--batch-mode`,`--offline`],e,60);if(t.timeSeconds>60)return{decisionCode:r.DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,stderr:i.truncateOutput(t.stderr),stdout:i.truncateOutput(t.stdout),exitStatus:t.status,timeSeconds:t.timeSeconds,memoryBytes:t.memoryBytes};if(t.status!==0)return{decisionCode:r.DecisionCode.BUILD_ERROR,stderr:y(t.stdout,t.stderr),stdout:i.truncateOutput(t.stdout),exitStatus:t.status,timeSeconds:t.timeSeconds,memoryBytes:t.memoryBytes}}function y(e,t){return i.truncateOutput(`${e}
|
|
8
|
+
${t}`.trim())}async function b(e,t){let n=o.default.join(t,`catalina-base`);await a.default.promises.cp(o.default.join(e,`conf`),o.default.join(n,`conf`),{recursive:!0,dereference:!0});for(let e of[`logs`,`temp`,`webapps`,`work`])await a.default.promises.mkdir(o.default.join(n,e),{recursive:!0});let r=o.default.join(n,`conf`,`server.xml`),i=await a.default.promises.readFile(r,`utf8`);return await a.default.promises.writeFile(r,i.replaceAll(`port="8080"`,`port="59000"`),`utf8`),n}async function x(e,t){let n=o.default.join(e,`target`,`${d}.war`),r=o.default.join(t,`webapps`),i=o.default.join(r,d),s=o.default.join(r,`${d}.war`);await a.default.promises.mkdir(r,{recursive:!0}),await a.default.promises.rm(i,{recursive:!0,force:!0}),await a.default.promises.rm(s,{force:!0}),await a.default.promises.copyFile(n,s)}async function S(e,t){let n=w([o.default.join(e,`bin`,`startup.sh`)],void 0,10,{...process.env,CATALINA_BASE:t,CATALINA_HOME:e});if(n.status!==0)throw Error(`Failed to start Tomcat: ${n.stderr.trim()||n.stdout.trim()}`);if(!await E(!0,1e4))throw Error(`Tomcat failed to start within 10 seconds`)}function C(e,t){let n=w([`bun`,`run`,`./judge.ts`,`--evaluate`],t,30,{...process.env,WORKING_DIRECTORY_PATH:e}),a=i.readOutputFiles(o.default.join(e,`__SCREENSHOTS.json`));return n.timeSeconds>30?{decisionCode:r.DecisionCode.TIME_LIMIT_EXCEEDED,stderr:i.truncateOutput(n.stderr),stdout:i.truncateOutput(n.stdout),exitStatus:n.status,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes,outputFiles:a}:n.stdout.length>5e4||n.stderr.length>5e4?{decisionCode:r.DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,stderr:i.truncateOutput(n.stderr),stdout:i.truncateOutput(n.stdout),exitStatus:n.status,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes,outputFiles:a}:n.status===0?{decisionCode:r.DecisionCode.ACCEPTED,stderr:i.truncateOutput(n.stderr)||void 0,stdout:i.truncateOutput(n.stdout)||void 0,exitStatus:n.status,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes,outputFiles:a}:{decisionCode:r.DecisionCode.WRONG_ANSWER,stderr:i.truncateOutput(n.stderr),stdout:i.truncateOutput(n.stdout),exitStatus:n.status,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes,outputFiles:a}}function w(e,t,n,r=process.env){let i=Date.now(),a=process.platform===`darwin`?`gtime`:`/usr/bin/time`,o=(0,c.spawnSync)(`timeout`,[n.toFixed(3),a,`--format`,`%e %M`,...e],{cwd:t,env:r,encoding:`utf8`}),s=(Date.now()-i)/1e3,l=[o.stderr,o.error?.message].filter(Boolean).join(`
|
|
9
|
+
`),u=/(?:^|\n)(\d+(?:\.\d+)?) (\d+)\s*$/u.exec(l),d=u?l.slice(0,u.index):l,f=Number(u?.[1])||s,p=1024*Number(u?.[2]??0);return{status:o.status===124?0:o.status??void 0,stdout:o.stdout,stderr:d,timeSeconds:o.status===124?n+.001:f,memoryBytes:p}}async function T(e,t){await D()&&(w([o.default.join(e,`bin`,`catalina.sh`),`stop`],void 0,10,{...process.env,CATALINA_BASE:t,CATALINA_HOME:e}),await E(!1,1e4))}async function E(e,t){let n=Date.now();for(;Date.now()-n<t;){if(await D()===e)return!0;await(0,u.setTimeout)(200)}return await D()===e}async function D(){return await new Promise(e=>{let t=l.default.get(f,t=>{t.resume();let n=t.statusCode??0;e(n>=200&&n<300||n===404)});t.on(`error`,()=>e(!1)),t.setTimeout(1e3,()=>t.destroy())})}function O(){let e=process.env.CATALINA_HOME;if(!e)throw Error(`CATALINA_HOME is not set`);return k(e),o.default.resolve(e)}function k(e){if(!a.default.existsSync(o.default.join(e,`bin`,`catalina.sh`))||!a.default.existsSync(o.default.join(e,`conf`,`server.xml`)))throw Error(`Invalid CATALINA_HOME path`)}function A(e){return`${f}/${d}${e.startsWith(`/`)?e:`/${e}`}`}exports.buildTomcatUrl=A,exports.tomcatJudgePreset=m;
|
|
10
10
|
//# sourceMappingURL=tomcat.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomcat.cjs","names":["parseArgs","fs","path","tmpdir","DecisionCode","spawnSync","sleep","http","z"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nimport { z } from 'zod';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst MAX_OUTPUT_LENGTH = 50_000;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nasync function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nfunction readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nfunction truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tomcat.cjs","names":["parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","truncateOutput","readOutputFiles","spawnSync","sleep","http"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
package/dist/presets/tomcat.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{printTestCaseResult as e}from"../helpers/printTestCaseResult.js";import{parseArgs as t}from"../helpers/parseArgs.js";import{DecisionCode as n}from"../types/decisionCode.js";import r from"
|
|
1
|
+
import{printTestCaseResult as e}from"../helpers/printTestCaseResult.js";import{parseArgs as t}from"../helpers/parseArgs.js";import{DecisionCode as n}from"../types/decisionCode.js";import{listSubmissionFiles as r,readOutputFiles as i,resolveJavaRelativePath as a,truncateOutput as o}from"../helpers/javaJudge.js";import s from"node:fs";import c from"node:path";import{tmpdir as l}from"node:os";import{spawnSync as u}from"node:child_process";import d from"node:http";import{setTimeout as f}from"node:timers/promises";const p=`judge`,m=`http://localhost:59000`,h=new Set([`.java`,`.jsp`,`.html`,`.htm`,`.xml`,`.js`,`.css`]);async function g(r){if(process.argv.includes(`--evaluate`)){try{await r.evaluate()}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1}return}let{cwd:i}=t(process.argv);if(!i)throw Error(`cwd argument required`);let a=`test1`,o=await y(i,r.forbiddenTexts??[]);if(o){e({testCaseId:a,...o});return}let u=await s.promises.mkdtemp(c.join(l(),`tomcat_judge_`)),d=c.join(u,`build`),f,p;try{await s.promises.mkdir(d,{recursive:!0}),await _(d,i,r);let t=b(d);if(t){e({testCaseId:a,...t});return}p=A(),f=await S(p,u),await D(p,f),await C(d,f),await w(p,f);let n=T(d,r.problemDirectoryPath);e({testCaseId:a,...n})}catch(t){e({testCaseId:a,decisionCode:n.JUDGE_NOT_AVAILABLE,stderr:t instanceof Error?t.message:String(t)})}finally{try{p&&f&&await D(p,f)}catch{}await s.promises.rm(u,{recursive:!0,force:!0})}}async function _(e,t,n){await s.promises.mkdir(c.join(e,`src/main/java`),{recursive:!0}),await s.promises.mkdir(c.join(e,`src/main/webapp/WEB-INF/jsp`),{recursive:!0}),await s.promises.copyFile(c.join(n.problemDirectoryPath,`pom.xml`),c.join(e,`pom.xml`));let i=await r(t);for(let r of i){let i=c.join(t,r),a=await v(e,r,i,n.jspDirectory);await s.promises.mkdir(c.dirname(a),{recursive:!0}),await s.promises.copyFile(i,a)}}async function v(e,t,n,r){let i=t.replaceAll(`\\`,`/`);if(i.startsWith(`src/main/`))return c.join(e,i);if(i.startsWith(`WEB-INF/`)||i.startsWith(`META-INF/`))return c.join(e,`src/main/webapp`,i);let o=c.extname(t).toLowerCase();return o===`.java`?c.join(e,`src/main/java`,await a(n,t)):o===`.jsp`?c.join(e,`src/main/webapp`,r,c.basename(t)):c.join(e,`src/main/webapp`,i)}async function y(e,t){let i=[];for(let n of await r(e)){if(!h.has(c.extname(n).toLowerCase()))continue;let r=await s.promises.readFile(c.join(e,n),`utf8`);for(let e of t)r.includes(e)&&i.push({path:n,pattern:e})}if(i.length!==0)return{decisionCode:n.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
|
|
2
2
|
ソースコードを修正してから再度提出してください。
|
|
3
3
|
|
|
4
4
|
| ファイル | 禁止パターン |
|
|
5
5
|
| -------- | ------------ |
|
|
6
|
-
`+
|
|
7
|
-
`)}}
|
|
8
|
-
${t}`.trim())}async function
|
|
9
|
-
`),
|
|
6
|
+
`+i.map(e=>`| \`${e.path}\` | \`${e.pattern}\` |`).join(`
|
|
7
|
+
`)}}function b(e){let t=E([`mvn`,`clean`,`package`,`--quiet`,`--batch-mode`,`--offline`],e,60);if(t.timeSeconds>60)return{decisionCode:n.BUILD_TIME_LIMIT_EXCEEDED,stderr:o(t.stderr),stdout:o(t.stdout),exitStatus:t.status,timeSeconds:t.timeSeconds,memoryBytes:t.memoryBytes};if(t.status!==0)return{decisionCode:n.BUILD_ERROR,stderr:x(t.stdout,t.stderr),stdout:o(t.stdout),exitStatus:t.status,timeSeconds:t.timeSeconds,memoryBytes:t.memoryBytes}}function x(e,t){return o(`${e}
|
|
8
|
+
${t}`.trim())}async function S(e,t){let n=c.join(t,`catalina-base`);await s.promises.cp(c.join(e,`conf`),c.join(n,`conf`),{recursive:!0,dereference:!0});for(let e of[`logs`,`temp`,`webapps`,`work`])await s.promises.mkdir(c.join(n,e),{recursive:!0});let r=c.join(n,`conf`,`server.xml`),i=await s.promises.readFile(r,`utf8`);return await s.promises.writeFile(r,i.replaceAll(`port="8080"`,`port="59000"`),`utf8`),n}async function C(e,t){let n=c.join(e,`target`,`${p}.war`),r=c.join(t,`webapps`),i=c.join(r,p),a=c.join(r,`${p}.war`);await s.promises.mkdir(r,{recursive:!0}),await s.promises.rm(i,{recursive:!0,force:!0}),await s.promises.rm(a,{force:!0}),await s.promises.copyFile(n,a)}async function w(e,t){let n=E([c.join(e,`bin`,`startup.sh`)],void 0,10,{...process.env,CATALINA_BASE:t,CATALINA_HOME:e});if(n.status!==0)throw Error(`Failed to start Tomcat: ${n.stderr.trim()||n.stdout.trim()}`);if(!await O(!0,1e4))throw Error(`Tomcat failed to start within 10 seconds`)}function T(e,t){let r=E([`bun`,`run`,`./judge.ts`,`--evaluate`],t,30,{...process.env,WORKING_DIRECTORY_PATH:e}),a=i(c.join(e,`__SCREENSHOTS.json`));return r.timeSeconds>30?{decisionCode:n.TIME_LIMIT_EXCEEDED,stderr:o(r.stderr),stdout:o(r.stdout),exitStatus:r.status,timeSeconds:r.timeSeconds,memoryBytes:r.memoryBytes,outputFiles:a}:r.stdout.length>5e4||r.stderr.length>5e4?{decisionCode:n.OUTPUT_SIZE_LIMIT_EXCEEDED,stderr:o(r.stderr),stdout:o(r.stdout),exitStatus:r.status,timeSeconds:r.timeSeconds,memoryBytes:r.memoryBytes,outputFiles:a}:r.status===0?{decisionCode:n.ACCEPTED,stderr:o(r.stderr)||void 0,stdout:o(r.stdout)||void 0,exitStatus:r.status,timeSeconds:r.timeSeconds,memoryBytes:r.memoryBytes,outputFiles:a}:{decisionCode:n.WRONG_ANSWER,stderr:o(r.stderr),stdout:o(r.stdout),exitStatus:r.status,timeSeconds:r.timeSeconds,memoryBytes:r.memoryBytes,outputFiles:a}}function E(e,t,n,r=process.env){let i=Date.now(),a=process.platform===`darwin`?`gtime`:`/usr/bin/time`,o=u(`timeout`,[n.toFixed(3),a,`--format`,`%e %M`,...e],{cwd:t,env:r,encoding:`utf8`}),s=(Date.now()-i)/1e3,c=[o.stderr,o.error?.message].filter(Boolean).join(`
|
|
9
|
+
`),l=/(?:^|\n)(\d+(?:\.\d+)?) (\d+)\s*$/u.exec(c),d=l?c.slice(0,l.index):c,f=Number(l?.[1])||s,p=1024*Number(l?.[2]??0);return{status:o.status===124?0:o.status??void 0,stdout:o.stdout,stderr:d,timeSeconds:o.status===124?n+.001:f,memoryBytes:p}}async function D(e,t){await k()&&(E([c.join(e,`bin`,`catalina.sh`),`stop`],void 0,10,{...process.env,CATALINA_BASE:t,CATALINA_HOME:e}),await O(!1,1e4))}async function O(e,t){let n=Date.now();for(;Date.now()-n<t;){if(await k()===e)return!0;await f(200)}return await k()===e}async function k(){return await new Promise(e=>{let t=d.get(m,t=>{t.resume();let n=t.statusCode??0;e(n>=200&&n<300||n===404)});t.on(`error`,()=>e(!1)),t.setTimeout(1e3,()=>t.destroy())})}function A(){let e=process.env.CATALINA_HOME;if(!e)throw Error(`CATALINA_HOME is not set`);return j(e),c.resolve(e)}function j(e){if(!s.existsSync(c.join(e,`bin`,`catalina.sh`))||!s.existsSync(c.join(e,`conf`,`server.xml`)))throw Error(`Invalid CATALINA_HOME path`)}function M(e){return`${m}/${p}${e.startsWith(`/`)?e:`/${e}`}`}export{M as buildTomcatUrl,g as tomcatJudgePreset};
|
|
10
10
|
//# sourceMappingURL=tomcat.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomcat.js","names":["sleep"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nimport { z } from 'zod';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst MAX_OUTPUT_LENGTH = 50_000;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nasync function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nfunction readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nfunction truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"uZAyBA,MAAM,EAAmB,QAGnB,EAAoB,IACpB,EAAkB,yBAClB,EAAyB,IAAI,IAAI,CAAC,QAAS,OAAQ,QAAS,OAAQ,OAAQ,MAAO,MAAM,CAAC,EAEhG,eAAsB,EAAkB,EAAkD,CACxF,GAAI,QAAQ,KAAK,SAAS,YAAY,EAAG,CACvC,GAAI,CACF,MAAM,EAAQ,SAAS,CACzB,OAAS,EAAO,CACd,QAAQ,MAAM,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAC,EACpE,QAAQ,SAAW,CACrB,CACA,MACF,CAEA,GAAM,CAAE,IAAK,GAAkB,EAAU,QAAQ,IAAI,EACrD,GAAI,CAAC,EAAe,MAAU,MAAM,uBAAuB,EAC3D,IAAM,EAAa,QACb,EAAuB,MAAM,EAAsB,EAAe,EAAQ,gBAAkB,CAAC,CAAC,EACpG,GAAI,EAAsB,CACxB,EAAoB,CAAE,aAAY,GAAG,CAAqB,CAAC,EAC3D,MACF,CAEA,IAAM,EAAa,MAAM,EAAG,SAAS,QAAQ,EAAK,KAAK,EAAO,EAAG,eAAe,CAAC,EAC3E,EAAW,EAAK,KAAK,EAAY,OAAO,EAC1C,EACA,EAEJ,GAAI,CACF,MAAM,EAAG,SAAS,MAAM,EAAU,CAAE,UAAW,EAAK,CAAC,EACrD,MAAM,EAAsB,EAAU,EAAe,CAAO,EAE5D,IAAM,EAAc,EAAe,CAAQ,EAC3C,GAAI,EAAa,CACf,EAAoB,CAAE,aAAY,GAAG,CAAY,CAAC,EAClD,MACF,CAEA,EAAe,EAAgB,EAC/B,EAAkB,MAAM,EAAsB,EAAc,CAAU,EACtE,MAAM,EAAoB,EAAc,CAAe,EACvD,MAAM,EAAc,EAAU,CAAe,EAC7C,MAAM,EAAY,EAAc,CAAe,EAE/C,IAAM,EAAc,EAAe,EAAU,EAAQ,oBAAoB,EACzE,EAAoB,CAAE,aAAY,GAAG,CAAY,CAAC,CACpD,OAAS,EAAO,CACd,EAAoB,CAClB,aACA,aAAc,EAAa,oBAC3B,OAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC/D,CAAC,CACH,QAAU,CACR,GAAI,CACE,GAAgB,GAClB,MAAM,EAAoB,EAAc,CAAe,CAE3D,MAAQ,CAER,CACA,MAAM,EAAG,SAAS,GAAG,EAAY,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACnE,CACF,CAEA,eAAe,EACb,EACA,EACA,EACe,CACf,MAAM,EAAG,SAAS,MAAM,EAAK,KAAK,EAAU,eAAe,EAAG,CAAE,UAAW,EAAK,CAAC,EACjF,MAAM,EAAG,SAAS,MAAM,EAAK,KAAK,EAAU,6BAA6B,EAAG,CAAE,UAAW,EAAK,CAAC,EAC/F,MAAM,EAAG,SAAS,SAAS,EAAK,KAAK,EAAQ,qBAAsB,SAAS,EAAG,EAAK,KAAK,EAAU,SAAS,CAAC,EAE7G,IAAM,EAAkB,MAAM,EAAoB,CAAa,EAC/D,IAAK,IAAM,KAAgB,EAAiB,CAC1C,IAAM,EAAa,EAAK,KAAK,EAAe,CAAY,EAClD,EAAa,MAAM,EAAkB,EAAU,EAAc,EAAY,EAAQ,YAAY,EACnG,MAAM,EAAG,SAAS,MAAM,EAAK,QAAQ,CAAU,EAAG,CAAE,UAAW,EAAK,CAAC,EACrE,MAAM,EAAG,SAAS,SAAS,EAAY,CAAU,CACnD,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAyB,EAAa,WAAW,KAAM,GAAG,EAChE,GAAI,EAAuB,WAAW,WAAW,EAC/C,OAAO,EAAK,KAAK,EAAU,CAAsB,EAEnD,GAAI,EAAuB,WAAW,UAAU,GAAK,EAAuB,WAAW,WAAW,EAChG,OAAO,EAAK,KAAK,EAAU,kBAAmB,CAAsB,EAGtE,IAAM,EAAY,EAAK,QAAQ,CAAY,CAAC,CAAC,YAAY,EAQzD,OAPI,IAAc,QACT,EAAK,KAAK,EAAU,gBAAiB,MAAM,EAAwB,EAAY,CAAY,CAAC,EAEjG,IAAc,OACT,EAAK,KAAK,EAAU,kBAAmB,EAAc,EAAK,SAAS,CAAY,CAAC,EAGlF,EAAK,KAAK,EAAU,kBAAmB,CAAsB,CACtE,CAEA,eAAe,EAAwB,EAAoB,EAA+C,CACxG,IAAM,EAAa,MAAM,EAAG,SAAS,SAAS,EAAY,MAAM,EAC1D,EAAe,sCAAsC,KAAK,CAAU,EAK1E,OAJK,IAAe,GAIb,EAAK,KAAK,EAAa,EAAE,CAAC,WAAW,IAAK,GAAG,EAAG,EAAK,SAAS,CAAoB,CAAC,EAHjF,CAIX,CAEA,eAAe,EACb,EACA,EAC6E,CAC7E,IAAM,EAAwD,CAAC,EAC/D,IAAK,IAAM,KAAgB,MAAM,EAAoB,CAAa,EAAG,CACnE,GAAI,CAAC,EAAuB,IAAI,EAAK,QAAQ,CAAY,CAAC,CAAC,YAAY,CAAC,EACtE,SAGF,IAAM,EAAa,MAAM,EAAG,SAAS,SAAS,EAAK,KAAK,EAAe,CAAY,EAAG,MAAM,EAC5F,IAAK,IAAM,KAAiB,EACtB,EAAW,SAAS,CAAa,GACnC,EAAiB,KAAK,CAAE,KAAM,EAAc,QAAS,CAAc,CAAC,CAG1E,CAEI,KAAiB,SAAW,EAIhC,MAAO,CACL,aAAc,EAAa,iCAC3B,iBACE;;;;;EACA,EAAiB,IAAK,GAAU,OAAO,EAAM,KAAK,SAAS,EAAM,QAAQ,KAAK,CAAC,CAAC,KAAK;CAAI,CAC7F,CACF,CAEA,eAAe,EAAoB,EAAoC,CACrE,IAAM,EAAsB,CAAC,EAE7B,OADA,MAAM,EAAuB,EAAS,GAAI,CAAS,EAC5C,EAAU,SAAS,CAC5B,CAEA,eAAe,EAAuB,EAAiB,EAAqB,EAAoC,CAC9G,IAAM,EAAa,EAAc,EAAK,KAAK,EAAS,CAAW,EAAI,EAC7D,EAAU,MAAM,EAAG,SAAS,QAAQ,EAAY,CAAE,cAAe,EAAK,CAAC,EAC7E,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAoB,EAAc,EAAK,KAAK,EAAa,EAAM,IAAI,EAAI,EAAM,KACnF,GAAI,EAAM,YAAY,EAAG,CACvB,MAAM,EAAuB,EAAS,EAAmB,CAAS,EAClE,QACF,CACI,EAAM,OAAO,GACf,EAAU,KAAK,CAAiB,CAEpC,CACF,CAEA,SAAS,EAAe,EAA2C,CACjE,IAAM,EAAgB,EACpB,CAAC,MAAO,QAAS,UAAW,UAAW,eAAgB,WAAW,EAClE,EACA,EACF,EACA,GAAI,EAAc,YAAc,GAC9B,MAAO,CACL,aAAc,EAAa,0BAC3B,OAAQ,EAAe,EAAc,MAAM,EAC3C,OAAQ,EAAe,EAAc,MAAM,EAC3C,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,WAC7B,EAEE,KAAc,SAAW,EAI7B,MAAO,CACL,aAAc,EAAa,YAC3B,OAAQ,EAAsB,EAAc,OAAQ,EAAc,MAAM,EACxE,OAAQ,EAAe,EAAc,MAAM,EAC3C,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,WAC7B,CACF,CAEA,SAAS,EAAsB,EAAgB,EAAwB,CACrE,OAAO,EACL,GAAG,EAAO;EACZ,IAAS,KAAK,CACd,CACF,CAEA,eAAe,EAAsB,EAAsB,EAAqC,CAC9F,IAAM,EAAkB,EAAK,KAAK,EAAY,eAAe,EAC7D,MAAM,EAAG,SAAS,GAAG,EAAK,KAAK,EAAc,MAAM,EAAG,EAAK,KAAK,EAAiB,MAAM,EAAG,CACxF,UAAW,GACX,YAAa,EACf,CAAC,EACD,IAAK,IAAM,IAAiB,CAAC,OAAQ,OAAQ,UAAW,MAAM,EAC5D,MAAM,EAAG,SAAS,MAAM,EAAK,KAAK,EAAiB,CAAa,EAAG,CAAE,UAAW,EAAK,CAAC,EAGxF,IAAM,EAAgB,EAAK,KAAK,EAAiB,OAAQ,YAAY,EAC/D,EAAY,MAAM,EAAG,SAAS,SAAS,EAAe,MAAM,EAElE,OADA,MAAM,EAAG,SAAS,UAAU,EAAe,EAAU,WAAW,cAAe,cAAc,EAAG,MAAM,EAC/F,CACT,CAEA,eAAe,EAAc,EAAkB,EAAwC,CACrF,IAAM,EAAc,EAAK,KAAK,EAAU,SAAU,GAAG,EAAiB,KAAK,EACrE,EAAa,EAAK,KAAK,EAAiB,SAAS,EACjD,EAAwB,EAAK,KAAK,EAAY,CAAgB,EAC9D,EAAkB,EAAK,KAAK,EAAY,GAAG,EAAiB,KAAK,EACvE,MAAM,EAAG,SAAS,MAAM,EAAY,CAAE,UAAW,EAAK,CAAC,EACvD,MAAM,EAAG,SAAS,GAAG,EAAuB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EAC5E,MAAM,EAAG,SAAS,GAAG,EAAiB,CAAE,MAAO,EAAK,CAAC,EACrD,MAAM,EAAG,SAAS,SAAS,EAAa,CAAe,CACzD,CAEA,eAAe,EAAY,EAAsB,EAAwC,CAEvF,IAAM,EAAgB,EAAW,CADP,EAAK,KAAK,EAAc,MAAO,YACP,CAAC,EAAG,IAAA,GAAW,GAAI,CACnE,GAAG,QAAQ,IACX,cAAe,EACf,cAAe,CACjB,CAAC,EACD,GAAI,EAAc,SAAW,EAC3B,MAAU,MAAM,2BAA2B,EAAc,OAAO,KAAK,GAAK,EAAc,OAAO,KAAK,GAAG,EAIzG,GAAI,CAAC,MADiB,EAAmB,GAAM,GAAM,EAEnD,MAAU,MAAM,0CAA0C,CAE9D,CAEA,SAAS,EAAe,EAAkB,EAA2C,CACnF,IAAM,EAAgB,EACpB,CAAC,MAAO,MAAO,aAAc,YAAY,EACzC,EACA,GACA,CACE,GAAG,QAAQ,IACX,uBAAwB,CAC1B,CACF,EAEM,EAAc,EAAgB,EAAK,KAAK,EAAU,oBAAoB,CAAC,EAmC7E,OAlCI,EAAc,YAAc,GACvB,CACL,aAAc,EAAa,oBAC3B,OAAQ,EAAe,EAAc,MAAM,EAC3C,OAAQ,EAAe,EAAc,MAAM,EAC3C,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,YAC3B,aACF,EAEE,EAAc,OAAO,OAAS,GAAqB,EAAc,OAAO,OAAS,EAC5E,CACL,aAAc,EAAa,2BAC3B,OAAQ,EAAe,EAAc,MAAM,EAC3C,OAAQ,EAAe,EAAc,MAAM,EAC3C,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,YAC3B,aACF,EAEE,EAAc,SAAW,EAYtB,CACL,aAAc,EAAa,SAC3B,OAAQ,EAAe,EAAc,MAAM,GAAK,IAAA,GAChD,OAAQ,EAAe,EAAc,MAAM,GAAK,IAAA,GAChD,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,YAC3B,aACF,EAnBS,CACL,aAAc,EAAa,aAC3B,OAAQ,EAAe,EAAc,MAAM,EAC3C,OAAQ,EAAe,EAAc,MAAM,EAC3C,WAAY,EAAc,OAC1B,YAAa,EAAc,YAC3B,YAAa,EAAc,YAC3B,aACF,CAYJ,CAEA,SAAS,EACP,EACA,EACA,EACA,EAAyB,QAAQ,IAClB,CACf,IAAM,EAAU,KAAK,IAAI,EACnB,EAAc,QAAQ,WAAa,SAAW,QAAU,gBAExD,EAAc,EAAU,UAAW,CAAC,EAAe,QAAQ,CAAC,EAAG,EAAa,WAAY,QAAS,GAAG,CAAO,EAAG,CAClH,MACA,MACA,SAAU,MACZ,CAAC,EACK,GAAkB,KAAK,IAAI,EAAI,GAAW,IAC1C,EAAkB,CAAC,EAAY,OAAQ,EAAY,OAAO,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK;CAAI,EAC5F,EAAc,qCAAqC,KAAK,CAAe,EACvE,EAAS,EAAc,EAAgB,MAAM,EAAG,EAAY,KAAK,EAAI,EACrE,EAAc,OAAO,IAAc,EAAE,GAAK,EAC1C,EAAc,KAAO,OAAO,IAAc,IAAM,CAAC,EAEvD,MAAO,CACL,OAAQ,EAAY,SAAW,IAAM,EAAK,EAAY,QAAU,IAAA,GAChE,OAAQ,EAAY,OACpB,SACA,YAAa,EAAY,SAAW,IAAM,EAAiB,KAAQ,EACnE,aACF,CACF,CAEA,eAAe,EAAoB,EAAsB,EAAwC,CACzF,MAAM,EAAgB,IAI5B,EAAW,CAAC,EAAK,KAAK,EAAc,MAAO,aAAa,EAAG,MAAM,EAAG,IAAA,GAAW,GAAI,CACjF,GAAG,QAAQ,IACX,cAAe,EACf,cAAe,CACjB,CAAC,EACD,MAAM,EAAmB,GAAO,GAAM,EACxC,CAEA,eAAe,EAAmB,EAA0B,EAAqC,CAC/F,IAAM,EAAY,KAAK,IAAI,EAC3B,KAAO,KAAK,IAAI,EAAI,EAAY,GAAW,CACzC,GAAK,MAAM,EAAgB,IAAO,EAChC,MAAO,GAET,MAAMA,EAAM,GAAG,CACjB,CACA,OAAQ,MAAM,EAAgB,IAAO,CACvC,CAEA,eAAe,GAAoC,CACjD,OAAO,MAAM,IAAI,QAAkB,GAAY,CAC7C,IAAM,EAAU,EAAK,IAAI,EAAkB,GAAa,CACtD,EAAS,OAAO,EAChB,IAAM,EAAS,EAAS,YAAc,EACtC,EAAS,GAAU,KAAO,EAAS,KAAQ,IAAW,GAAG,CAC3D,CAAC,EACD,EAAQ,GAAG,YAAe,EAAQ,EAAK,CAAC,EACxC,EAAQ,WAAW,QAAY,EAAQ,QAAQ,CAAC,CAClD,CAAC,CACH,CAEA,SAAS,GAA0B,CACjC,IAAM,EAAe,QAAQ,IAAI,cACjC,GAAI,CAAC,EACH,MAAU,MAAM,0BAA0B,EAI5C,OADA,EAAqB,CAAY,EAC1B,EAAK,QAAQ,CAAY,CAClC,CAEA,SAAS,EAAqB,EAA4B,CACxD,GACE,CAAC,EAAG,WAAW,EAAK,KAAK,EAAc,MAAO,aAAa,CAAC,GAC5D,CAAC,EAAG,WAAW,EAAK,KAAK,EAAc,OAAQ,YAAY,CAAC,EAE5D,MAAU,MAAM,4BAA4B,CAEhD,CAEA,SAAS,EAAgB,EAA4C,CACnE,GAAI,CAAC,EAAG,WAAW,CAAQ,EACzB,OAIF,IAAM,EAAkB,KAAK,MAAM,EAAG,aAAa,EAAU,MAAM,CAAC,EAC9D,EAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAM,EACpD,GAAI,CAAC,EAAO,QAAS,OACrB,IAAM,EAAmB,EAAE,OAAO,CAChC,KAAM,EAAE,OAAO,EACf,KAAM,EAAE,OAAO,EACf,SAAU,EAAE,QAAQ,QAAQ,CAAC,CAAC,SAAS,CACzC,CAAC,EACD,OAAO,EAAO,KAAK,QAAS,GAAU,CACpC,IAAM,EAAS,EAAiB,UAAU,CAAK,EAC/C,OAAO,EAAO,QAAU,CAAC,EAAO,IAAI,EAAI,CAAC,CAC3C,CAAC,CACH,CAEA,SAAS,EAAe,EAAuB,CAC7C,OAAO,EAAM,MAAM,EAAG,CAAiB,CACzC,CAEA,SAAgB,EAAe,EAAyB,CACtD,MAAO,GAAG,EAAgB,GAAG,IAAmB,EAAQ,WAAW,GAAG,EAAI,EAAU,IAAI,KAC1F"}
|
|
1
|
+
{"version":3,"file":"tomcat.js","names":["sleep"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: error instanceof Error ? error.message : String(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exercode/problem-utils",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": ":100: A set of utilities for judging programs on Exercode (https://exercode.willbooster.com/).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"exercode",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"zod": "4.5.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"build-ts": "21.0.12"
|
|
50
|
+
"build-ts": "21.0.12",
|
|
51
|
+
"undici-types": "8.3.0"
|
|
51
52
|
},
|
|
52
53
|
"engines": {
|
|
53
54
|
"node": ">=24"
|