Merge shared code for submission running (extension/sandbox) and team submission info (extension/web) (#16)
* Unify submission execution implementations into submissionRunner * Unify contestMonitorTypes definitions between extension & web * Make line separator in entry use LF * Add entry.sh for sandbox * Fix web imports * Sandbox read from .env --------- Co-authored-by: orosmatthew <orosmatthew@pm.me>
This commit is contained in:
parent
6e95a955a8
commit
22bc7460df
22
.dockerignore
Normal file
22
.dockerignore
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
sandbox/node_modules
|
||||||
|
sandbox/build
|
||||||
|
sandbox/.env
|
||||||
|
|
||||||
|
web/.DS_Store
|
||||||
|
web/node_modules
|
||||||
|
web/build
|
||||||
|
web/.svelte-kit
|
||||||
|
web/package
|
||||||
|
web/.env
|
||||||
|
web/.env.*
|
||||||
|
web/!.env.example
|
||||||
|
web/vite.config.js.timestamp-*
|
||||||
|
web/vite.config.ts.timestamp-*
|
||||||
|
web/temp
|
||||||
|
web/db
|
||||||
|
|
||||||
|
shared/submissionRunner/node_modules
|
||||||
|
shared/submissionRunner/build
|
||||||
|
|
||||||
|
shared/extensionWeb/node_modules
|
||||||
|
shared/extensionWeb/build
|
@ -7,7 +7,7 @@ import {
|
|||||||
ProblemNameForExtension,
|
ProblemNameForExtension,
|
||||||
FullStateForExtension,
|
FullStateForExtension,
|
||||||
SubmissionForExtension
|
SubmissionForExtension
|
||||||
} from './contestMonitorSharedTypes';
|
} from '@extensionWeb/contestMonitorTypes.cjs';
|
||||||
import { LiteEvent } from '../utilities/LiteEvent';
|
import { LiteEvent } from '../utilities/LiteEvent';
|
||||||
|
|
||||||
export type ContestTeamState = {
|
export type ContestTeamState = {
|
||||||
|
@ -2,11 +2,11 @@ import * as vscode from 'vscode';
|
|||||||
import { getNonce } from './getNonce';
|
import { getNonce } from './getNonce';
|
||||||
import urlJoin from 'url-join';
|
import urlJoin from 'url-join';
|
||||||
import { extensionSettings } from './extension';
|
import { extensionSettings } from './extension';
|
||||||
import { runJava } from './run/java';
|
import { runJava } from '@submissionRunner/java.cjs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { submitProblem } from './submit';
|
import { submitProblem } from './submit';
|
||||||
import { runCSharp } from './run/csharp';
|
import { runCSharp } from '@submissionRunner/csharp.cjs';
|
||||||
import { runCpp } from './run/cpp';
|
import { runCpp } from '@submissionRunner/cpp.cjs';
|
||||||
import { TeamData } from './sharedTypes';
|
import { TeamData } from './sharedTypes';
|
||||||
import outputPanelLog from './outputPanelLog';
|
import outputPanelLog from './outputPanelLog';
|
||||||
import { recordInitialSubmission } from './contestMonitor/contestStateSyncManager';
|
import { recordInitialSubmission } from './contestMonitor/contestStateSyncManager';
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import { exec, spawn } from 'child_process';
|
|
||||||
import {
|
|
||||||
timeoutSeconds,
|
|
||||||
type IRunner,
|
|
||||||
type IRunnerParams,
|
|
||||||
type IRunnerReturn,
|
|
||||||
type RunResult
|
|
||||||
} from './types';
|
|
||||||
import kill = require('tree-kill');
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import * as util from 'util';
|
|
||||||
|
|
||||||
const execPromise = util.promisify(exec);
|
|
||||||
|
|
||||||
export type CppPlatform = 'VisualStudio' | 'GCC';
|
|
||||||
|
|
||||||
interface IRunnerParamsCpp extends IRunnerParams {
|
|
||||||
srcDir: string;
|
|
||||||
problemName: string;
|
|
||||||
input: string;
|
|
||||||
cppPlatform: CppPlatform;
|
|
||||||
outputCallback?: (data: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runCpp: IRunner<IRunnerParamsCpp> = async function (
|
|
||||||
params: IRunnerParamsCpp
|
|
||||||
): IRunnerReturn {
|
|
||||||
const tmpDir = os.tmpdir();
|
|
||||||
const buildDir = join(tmpDir, 'bwcontest-cpp');
|
|
||||||
if (fs.existsSync(buildDir)) {
|
|
||||||
fs.removeSync(buildDir);
|
|
||||||
}
|
|
||||||
fs.mkdirSync(buildDir);
|
|
||||||
|
|
||||||
console.log(`- BUILD: ${params.problemName}`);
|
|
||||||
|
|
||||||
const configureCommand = `cmake -S ${params.srcDir} -B ${buildDir}`;
|
|
||||||
try {
|
|
||||||
await execPromise(configureCommand);
|
|
||||||
} catch (e) {
|
|
||||||
const buildErrorText = e?.toString() ?? 'Unknown build errors.';
|
|
||||||
console.log('Build errors: ' + buildErrorText);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
runResult: { kind: 'CompileFailed', resultKindReason: buildErrorText }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const compileCommand = `cmake --build ${buildDir} --target ${params.problemName}`;
|
|
||||||
try {
|
|
||||||
await execPromise(compileCommand);
|
|
||||||
} catch (e) {
|
|
||||||
const buildErrorText = e?.toString() ?? 'Unknown build errors.';
|
|
||||||
console.log('Build errors: ' + buildErrorText);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
runResult: { kind: 'CompileFailed', resultKindReason: buildErrorText }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`- RUN: ${params.problemName}`);
|
|
||||||
|
|
||||||
let runCommand = '';
|
|
||||||
if (params.cppPlatform === 'VisualStudio') {
|
|
||||||
runCommand = `${join(buildDir, 'Debug', `${params.problemName}.exe`)}`;
|
|
||||||
} else {
|
|
||||||
runCommand = `${join(buildDir, params.problemName)}`;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let outputBuffer = '';
|
|
||||||
const child = spawn(runCommand, { shell: true });
|
|
||||||
child.stdout.setEncoding('utf8');
|
|
||||||
child.stdout.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
params.outputCallback?.(data.toString());
|
|
||||||
});
|
|
||||||
child.stderr.setEncoding('utf8');
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
params.outputCallback?.(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
const runStartTime = performance.now();
|
|
||||||
child.stdin.write(params.input);
|
|
||||||
child.stdin.end();
|
|
||||||
|
|
||||||
let timeLimitExceeded = false;
|
|
||||||
let completedNormally = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
runResult: new Promise<RunResult>((resolve) => {
|
|
||||||
child.on('close', () => {
|
|
||||||
completedNormally = !timeLimitExceeded;
|
|
||||||
|
|
||||||
const runEndTime = performance.now();
|
|
||||||
const runtimeMilliseconds = Math.floor(runEndTime - runStartTime);
|
|
||||||
|
|
||||||
if (completedNormally) {
|
|
||||||
clearTimeout(timeoutHandle);
|
|
||||||
resolve({
|
|
||||||
kind: 'Completed',
|
|
||||||
output: outputBuffer,
|
|
||||||
exitCode: child.exitCode!,
|
|
||||||
runtimeMilliseconds
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`);
|
|
||||||
resolve({
|
|
||||||
kind: 'TimeLimitExceeded',
|
|
||||||
output: outputBuffer,
|
|
||||||
resultKindReason: `Timeout after ${timeoutSeconds} seconds`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeoutHandle = setTimeout(() => {
|
|
||||||
if (completedNormally) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`);
|
|
||||||
timeLimitExceeded = true;
|
|
||||||
|
|
||||||
child.stdin.end();
|
|
||||||
child.stdin.destroy();
|
|
||||||
child.stdout.destroy();
|
|
||||||
child.stderr.destroy();
|
|
||||||
child.kill('SIGKILL');
|
|
||||||
}, timeoutSeconds * 1000);
|
|
||||||
}),
|
|
||||||
killFunc() {
|
|
||||||
if (child.pid !== undefined) {
|
|
||||||
if (!completedNormally && !timeLimitExceeded) {
|
|
||||||
kill(child.pid);
|
|
||||||
params.outputCallback?.('\n[Manually stopped]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, runResult: { kind: 'RunError' } };
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,89 +0,0 @@
|
|||||||
import { spawn } from 'child_process';
|
|
||||||
import kill = require('tree-kill');
|
|
||||||
import { timeoutSeconds, type IRunner, type IRunnerReturn, type RunResult } from './types';
|
|
||||||
|
|
||||||
export const runCSharp: IRunner = async function (params: {
|
|
||||||
srcDir: string;
|
|
||||||
input: string;
|
|
||||||
outputCallback?: (data: string) => void;
|
|
||||||
}): IRunnerReturn {
|
|
||||||
console.log(`- RUN: ${params.srcDir}`);
|
|
||||||
const child = spawn('dotnet run', { shell: true, cwd: params.srcDir });
|
|
||||||
|
|
||||||
try {
|
|
||||||
let outputBuffer = '';
|
|
||||||
child.stdout.setEncoding('utf8');
|
|
||||||
child.stdout.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
params.outputCallback?.(data.toString());
|
|
||||||
});
|
|
||||||
child.stderr.setEncoding('utf8');
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
params.outputCallback?.(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
const runStartTime = performance.now();
|
|
||||||
child.stdin.write(params.input);
|
|
||||||
child.stdin.end();
|
|
||||||
|
|
||||||
let timeLimitExceeded = false;
|
|
||||||
let completedNormally = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
runResult: new Promise<RunResult>((resolve) => {
|
|
||||||
child.on('close', () => {
|
|
||||||
completedNormally = !timeLimitExceeded;
|
|
||||||
|
|
||||||
const runEndTime = performance.now();
|
|
||||||
const runtimeMilliseconds = Math.floor(runEndTime - runStartTime);
|
|
||||||
|
|
||||||
if (completedNormally) {
|
|
||||||
clearTimeout(timeoutHandle);
|
|
||||||
resolve({
|
|
||||||
kind: 'Completed',
|
|
||||||
output: outputBuffer,
|
|
||||||
exitCode: child.exitCode!,
|
|
||||||
runtimeMilliseconds
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`);
|
|
||||||
resolve({
|
|
||||||
kind: 'TimeLimitExceeded',
|
|
||||||
output: outputBuffer,
|
|
||||||
resultKindReason: `Timeout after ${timeoutSeconds} seconds`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeoutHandle = setTimeout(() => {
|
|
||||||
if (completedNormally) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`);
|
|
||||||
timeLimitExceeded = true;
|
|
||||||
|
|
||||||
child.stdin.end();
|
|
||||||
child.stdin.destroy();
|
|
||||||
child.stdout.destroy();
|
|
||||||
child.stderr.destroy();
|
|
||||||
if (child.pid !== undefined) {
|
|
||||||
kill(child.pid);
|
|
||||||
}
|
|
||||||
}, timeoutSeconds * 1000);
|
|
||||||
}),
|
|
||||||
killFunc() {
|
|
||||||
if (child.pid !== undefined) {
|
|
||||||
if (!completedNormally && !timeLimitExceeded) {
|
|
||||||
kill(child.pid);
|
|
||||||
params.outputCallback?.('\n[Manually stopped]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, runResult: { kind: 'RunError' } };
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
export const timeoutSeconds = 30;
|
|
||||||
|
|
||||||
export type RunResultKind =
|
|
||||||
| 'CompileFailed'
|
|
||||||
| 'TimeLimitExceeded'
|
|
||||||
| 'Completed'
|
|
||||||
| 'SandboxError'
|
|
||||||
| 'RunError';
|
|
||||||
|
|
||||||
export type RunResult = {
|
|
||||||
kind: RunResultKind;
|
|
||||||
output?: string;
|
|
||||||
exitCode?: number;
|
|
||||||
runtimeMilliseconds?: number;
|
|
||||||
resultKindReason?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IRunnerParams {
|
|
||||||
srcDir: string;
|
|
||||||
input: string;
|
|
||||||
outputCallback?: (data: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IRunnerReturn = Promise<
|
|
||||||
| { success: true; killFunc: () => void; runResult: Promise<RunResult> }
|
|
||||||
| { success: false; runResult: RunResult }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface IRunner<T extends IRunnerParams = IRunnerParams> {
|
|
||||||
(params: T): IRunnerReturn;
|
|
||||||
}
|
|
@ -6,11 +6,19 @@
|
|||||||
"lib": ["ES2020"],
|
"lib": ["ES2020"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": true /* enable all strict type-checking options */
|
"strict": true, /* enable all strict type-checking options */
|
||||||
/* Additional Checks */
|
/* Additional Checks */
|
||||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
"paths": {
|
||||||
|
"@submissionRunner/*": ["../../shared/submissionRunner/*"],
|
||||||
|
"@extensionWeb/*": ["../../shared/extensionWeb/*"]
|
||||||
},
|
},
|
||||||
"exclude": ["webviews"]
|
},
|
||||||
|
"exclude": ["webviews"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../../shared/submissionRunner" },
|
||||||
|
{ "path": "../../shared/extensionWeb" }
|
||||||
|
]
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
WORKDIR /app
|
# Setup
|
||||||
|
|
||||||
|
RUN mkdir sandbox
|
||||||
|
WORKDIR /app/sandbox
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|
||||||
@ -20,10 +23,28 @@ ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
|
|||||||
RUN git config --global user.name "Admin"
|
RUN git config --global user.name "Admin"
|
||||||
RUN git config --global user.email noemail@example.com
|
RUN git config --global user.email noemail@example.com
|
||||||
|
|
||||||
WORKDIR /app
|
# Prep Sandbox
|
||||||
|
|
||||||
COPY package*.json ./
|
WORKDIR /app/sandbox
|
||||||
|
|
||||||
|
COPY ./sandbox/package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY ./sandbox/ .
|
||||||
|
|
||||||
|
# Prep SubmissionRunner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir shared
|
||||||
|
RUN mkdir shared/submissionRunner
|
||||||
|
WORKDIR /app/shared/submissionRunner
|
||||||
|
|
||||||
|
COPY ./shared/submissionRunner/package*.json .
|
||||||
|
RUN npm install
|
||||||
|
COPY ./shared/submissionRunner/ .
|
||||||
|
|
||||||
|
# Build/Run
|
||||||
|
|
||||||
|
WORKDIR /app/sandbox
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
CMD ["node", "build"]
|
RUN chmod +x ./docker/entry.sh
|
||||||
|
CMD ["./docker/entry.sh"]
|
@ -1,7 +1,9 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
sandbox:
|
sandbox:
|
||||||
build: .
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: ./sandbox/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
- ADMIN_URL=${ADMIN_URL}
|
- ADMIN_URL=${ADMIN_URL}
|
||||||
- REPO_URL=${REPO_URL}
|
- REPO_URL=${REPO_URL}
|
||||||
|
3
sandbox/docker/entry.sh
Normal file
3
sandbox/docker/entry.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# THIS FILE MUST USE LF LINE SEPARATORS!
|
||||||
|
node ./build/sandbox.cjs
|
1068
sandbox/package-lock.json
generated
1068
sandbox/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,18 +5,23 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "esbuild src/index.ts --bundle --outfile=build/sandbox.cjs --format=cjs --platform=node",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "prettier --check . && eslint .",
|
"lint": "prettier --check . && eslint .",
|
||||||
"start": "node build"
|
"start": "node ./build/sandbox.cjs"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/node": "^20.11.2",
|
"@types/node": "^20.11.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||||
"@typescript-eslint/parser": "^7.0.1",
|
"@typescript-eslint/parser": "^7.0.1",
|
||||||
|
"esbuild": "^0.19.11",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"prettier": "^3.2.2",
|
"prettier": "^3.2.2",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
@ -24,6 +29,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
|
"rollup": "^4.12.1",
|
||||||
"simple-git": "^3.22.0",
|
"simple-git": "^3.22.0",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"url-join": "^5.0.0",
|
"url-join": "^5.0.0",
|
||||||
|
@ -1,39 +1,19 @@
|
|||||||
import dotenv from 'dotenv';
|
import 'dotenv/config';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import urlJoin from 'url-join';
|
import urlJoin from 'url-join';
|
||||||
import { z } from 'zod';
|
|
||||||
import os, { EOL } from 'os';
|
import os, { EOL } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { simpleGit, SimpleGit } from 'simple-git';
|
import { simpleGit, SimpleGit } from 'simple-git';
|
||||||
import { runJava } from './run/java.js';
|
import { runJava } from '@submissionRunner/java.cjs';
|
||||||
import { runCSharp } from './run/csharp.js';
|
import { runCSharp } from '@submissionRunner/csharp.cjs';
|
||||||
import { runCpp } from './run/cpp.js';
|
import { runCpp } from '@submissionRunner/cpp.cjs';
|
||||||
|
import { RunResult, RunResultZod } from '@submissionRunner/types.cjs';
|
||||||
export const timeoutSeconds = 30;
|
import { z } from 'zod';
|
||||||
|
|
||||||
const RunResultKind = z.enum([
|
|
||||||
'CompileFailed',
|
|
||||||
'TimeLimitExceeded',
|
|
||||||
'Completed',
|
|
||||||
'SandboxError',
|
|
||||||
'RunError'
|
|
||||||
]);
|
|
||||||
export type RunResultKind = z.infer<typeof RunResultKind>;
|
|
||||||
|
|
||||||
const RunResult = z
|
|
||||||
.object({
|
|
||||||
kind: RunResultKind,
|
|
||||||
output: z.string().optional(),
|
|
||||||
exitCode: z.number().optional(),
|
|
||||||
runtimeMilliseconds: z.number().optional(),
|
|
||||||
resultKindReason: z.string().optional()
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
const submissionPostData = z
|
const submissionPostData = z
|
||||||
.object({
|
.object({
|
||||||
submissionId: z.number(),
|
submissionId: z.number(),
|
||||||
result: RunResult
|
result: RunResultZod
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
@ -59,7 +39,6 @@ const submissionGetData = z
|
|||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export type RunResult = z.infer<typeof RunResult>;
|
|
||||||
type SubmissionGetData = z.infer<typeof submissionGetData>;
|
type SubmissionGetData = z.infer<typeof submissionGetData>;
|
||||||
type SubmissionPostData = z.infer<typeof submissionPostData>;
|
type SubmissionPostData = z.infer<typeof submissionPostData>;
|
||||||
|
|
||||||
@ -78,10 +57,12 @@ async function fetchQueuedSubmission(): Promise<SubmissionGetData | undefined> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = submissionGetData.parse(await res.json());
|
const json = await res.json();
|
||||||
|
const data = submissionGetData.parse(json);
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,9 +186,14 @@ function validateEnv(): boolean {
|
|||||||
return process.env.ADMIN_URL !== undefined && process.env.REPO_URL !== undefined;
|
return process.env.ADMIN_URL !== undefined && process.env.REPO_URL !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
if (!validateEnv()) {
|
if (!validateEnv()) {
|
||||||
|
console.log(process.env);
|
||||||
|
console.log(
|
||||||
|
'process.env.ADMIN_URL is ' +
|
||||||
|
process.env.ADMIN_URL +
|
||||||
|
' and process.env.REPO_URL is ' +
|
||||||
|
process.env.REPO_URL
|
||||||
|
);
|
||||||
throw Error('Invalid environment');
|
throw Error('Invalid environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +273,7 @@ async function run() {
|
|||||||
break;
|
break;
|
||||||
case SubmissionProcessingResult.NoSubmissions:
|
case SubmissionProcessingResult.NoSubmissions:
|
||||||
if (iterationsSinceProcessedSubmission > 0 && iterationsSinceProcessedSubmission % 6 == 0) {
|
if (iterationsSinceProcessedSubmission > 0 && iterationsSinceProcessedSubmission % 6 == 0) {
|
||||||
|
{
|
||||||
const numMinutes = iterationsSinceProcessedSubmission / 6;
|
const numMinutes = iterationsSinceProcessedSubmission / 6;
|
||||||
console.log(
|
console.log(
|
||||||
`${numMinutes} minute${numMinutes > 1 ? 's' : ''} since ` +
|
`${numMinutes} minute${numMinutes > 1 ? 's' : ''} since ` +
|
||||||
@ -297,6 +284,7 @@ async function run() {
|
|||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||||
iterationsSinceProcessedSubmission++;
|
iterationsSinceProcessedSubmission++;
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import { exec, spawn } from 'child_process';
|
|
||||||
import util from 'util';
|
|
||||||
import { RunResult, timeoutSeconds } from '../index.js';
|
|
||||||
import { IRunner, IRunnerParams, IRunnerReturn } from './types.js';
|
|
||||||
import kill from 'tree-kill';
|
|
||||||
|
|
||||||
const execPromise = util.promisify(exec);
|
|
||||||
|
|
||||||
interface IRunnerParamsJava extends IRunnerParams {
|
|
||||||
srcDir: string;
|
|
||||||
mainFile: string;
|
|
||||||
mainClass: string;
|
|
||||||
input: string;
|
|
||||||
outputCallback?: (data: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runJava: IRunner<IRunnerParamsJava> = async function (
|
|
||||||
params: IRunnerParamsJava
|
|
||||||
): IRunnerReturn {
|
|
||||||
console.log(`- BUILD: ${params.mainFile}`);
|
|
||||||
const compileCommand = `javac -cp ${join(params.srcDir, 'src')} ${params.mainFile} -d ${join(params.srcDir, 'build')}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await execPromise(compileCommand);
|
|
||||||
} catch (e) {
|
|
||||||
const buildErrorText = e?.toString() ?? 'Unknown build errors.';
|
|
||||||
console.log('Build errors: ' + buildErrorText);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
runResult: { kind: 'CompileFailed', resultKindReason: buildErrorText }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`- RUN: ${params.mainClass}`);
|
|
||||||
const runCommand = `java -cp "${join(params.srcDir, 'build')}" ${params.mainClass}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let outputBuffer = '';
|
|
||||||
const child = spawn(runCommand, { shell: true });
|
|
||||||
child.stdout.setEncoding('utf8');
|
|
||||||
child.stdout.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
});
|
|
||||||
child.stderr.setEncoding('utf8');
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
outputBuffer += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
const runStartTime = performance.now();
|
|
||||||
child.stdin.write(params.input);
|
|
||||||
child.stdin.end();
|
|
||||||
|
|
||||||
let timeLimitExceeded = false;
|
|
||||||
let completedNormally = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
runResult: new Promise<RunResult>((resolve) => {
|
|
||||||
child.on('close', () => {
|
|
||||||
completedNormally = !timeLimitExceeded;
|
|
||||||
|
|
||||||
const runEndTime = performance.now();
|
|
||||||
const runtimeMilliseconds = Math.floor(runEndTime - runStartTime);
|
|
||||||
|
|
||||||
if (completedNormally) {
|
|
||||||
clearTimeout(timeoutHandle);
|
|
||||||
resolve({
|
|
||||||
kind: 'Completed',
|
|
||||||
output: outputBuffer,
|
|
||||||
exitCode: child.exitCode!,
|
|
||||||
runtimeMilliseconds
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`);
|
|
||||||
resolve({
|
|
||||||
kind: 'TimeLimitExceeded',
|
|
||||||
output: outputBuffer,
|
|
||||||
resultKindReason: `Timeout after ${timeoutSeconds} seconds`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeoutHandle = setTimeout(() => {
|
|
||||||
if (completedNormally) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`);
|
|
||||||
timeLimitExceeded = true;
|
|
||||||
|
|
||||||
child.stdin.end();
|
|
||||||
child.stdin.destroy();
|
|
||||||
child.stdout.destroy();
|
|
||||||
child.stderr.destroy();
|
|
||||||
child.kill('SIGKILL');
|
|
||||||
}, timeoutSeconds * 1000);
|
|
||||||
}),
|
|
||||||
killFunc() {
|
|
||||||
if (child.pid !== undefined) {
|
|
||||||
if (!completedNormally && !timeLimitExceeded) {
|
|
||||||
kill(child.pid);
|
|
||||||
params.outputCallback?.('\n[Manually stopped]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, runResult: { kind: 'RunError' } };
|
|
||||||
}
|
|
||||||
};
|
|
16
sandbox/src/run/types.d.ts
vendored
16
sandbox/src/run/types.d.ts
vendored
@ -1,16 +0,0 @@
|
|||||||
import { RunResult } from '../index.ts';
|
|
||||||
|
|
||||||
interface IRunnerParams {
|
|
||||||
srcDir: string;
|
|
||||||
input: string;
|
|
||||||
outputCallback?: (data: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type IRunnerReturn = Promise<
|
|
||||||
| { success: true; killFunc: () => void; runResult: Promise<RunResult> }
|
|
||||||
| { success: false; runResult: RunResult }
|
|
||||||
>;
|
|
||||||
|
|
||||||
interface IRunner<T extends IRunnerParams = IRunnerParams> {
|
|
||||||
(params: T): IRunnerReturn;
|
|
||||||
}
|
|
@ -4,7 +4,10 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"strict": true
|
"strict": true,
|
||||||
},
|
"paths": {
|
||||||
"include": ["./src/**/*"]
|
"@submissionRunner/*": ["../shared/submissionRunner/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": [{ "path": "../shared/submissionRunner" }]
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
build
|
/build
|
||||||
.env
|
|
10
shared/extensionWeb/package-lock.json
generated
Normal file
10
shared/extensionWeb/package-lock.json
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "extensionWeb",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
shared/extensionWeb/package.json
Normal file
9
shared/extensionWeb/package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
14
shared/extensionWeb/tsconfig.json
Normal file
14
shared/extensionWeb/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./build",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
},
|
||||||
|
"include": ["/**/*.cts"]
|
||||||
|
}
|
2
shared/submissionRunner/.gitignore
vendored
Normal file
2
shared/submissionRunner/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
/build
|
@ -1,8 +1,8 @@
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { exec, spawn } from 'child_process';
|
import { exec, spawn } from 'child_process';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import { RunResult, timeoutSeconds } from '../index.js';
|
import type { IRunner, IRunnerParams, IRunnerReturn, RunResult } from './types.cjs';
|
||||||
import { IRunner, IRunnerParams, IRunnerReturn } from './types.js';
|
import { timeoutSeconds } from './settings.cjs';
|
||||||
import kill from 'tree-kill';
|
import kill from 'tree-kill';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
@ -21,7 +21,7 @@ interface IRunnerParamsCpp extends IRunnerParams {
|
|||||||
|
|
||||||
export const runCpp: IRunner<IRunnerParamsCpp> = async function (
|
export const runCpp: IRunner<IRunnerParamsCpp> = async function (
|
||||||
params: IRunnerParamsCpp
|
params: IRunnerParamsCpp
|
||||||
): IRunnerReturn {
|
): Promise<IRunnerReturn> {
|
||||||
const tmpDir = os.tmpdir();
|
const tmpDir = os.tmpdir();
|
||||||
const buildDir = join(tmpDir, 'bwcontest-cpp');
|
const buildDir = join(tmpDir, 'bwcontest-cpp');
|
||||||
if (fs.existsSync(buildDir)) {
|
if (fs.existsSync(buildDir)) {
|
@ -1,13 +1,13 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import kill from 'tree-kill';
|
import kill from 'tree-kill';
|
||||||
import { RunResult, timeoutSeconds } from '../index.js';
|
import type { IRunner, IRunnerReturn, RunResult } from './types.cjs';
|
||||||
import { IRunner, IRunnerReturn } from './types.js';
|
import { timeoutSeconds } from './settings.cjs';
|
||||||
|
|
||||||
export const runCSharp: IRunner = async function (params: {
|
export const runCSharp: IRunner = async function (params: {
|
||||||
srcDir: string;
|
srcDir: string;
|
||||||
input: string;
|
input: string;
|
||||||
outputCallback?: (data: string) => void;
|
outputCallback?: (data: string) => void;
|
||||||
}): IRunnerReturn {
|
}): Promise<IRunnerReturn> {
|
||||||
console.log(`- RUN: ${params.srcDir}`);
|
console.log(`- RUN: ${params.srcDir}`);
|
||||||
const child = spawn('dotnet run', { shell: true, cwd: params.srcDir });
|
const child = spawn('dotnet run', { shell: true, cwd: params.srcDir });
|
||||||
|
|
@ -1,13 +1,9 @@
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { exec, spawn } from 'child_process';
|
import { exec, spawn } from 'child_process';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import {
|
import type { IRunner, IRunnerParams, IRunnerReturn, RunResult } from './types.cjs';
|
||||||
timeoutSeconds,
|
import { timeoutSeconds } from './settings.cjs';
|
||||||
type IRunner,
|
|
||||||
type IRunnerParams,
|
|
||||||
type IRunnerReturn,
|
|
||||||
type RunResult
|
|
||||||
} from './types';
|
|
||||||
import kill = require('tree-kill');
|
import kill = require('tree-kill');
|
||||||
|
|
||||||
const execPromise = util.promisify(exec);
|
const execPromise = util.promisify(exec);
|
||||||
@ -22,7 +18,7 @@ interface IRunnerParamsJava extends IRunnerParams {
|
|||||||
|
|
||||||
export const runJava: IRunner<IRunnerParamsJava> = async function (
|
export const runJava: IRunner<IRunnerParamsJava> = async function (
|
||||||
params: IRunnerParamsJava
|
params: IRunnerParamsJava
|
||||||
): IRunnerReturn {
|
): Promise<IRunnerReturn> {
|
||||||
console.log(`- BUILD: ${params.mainFile}`);
|
console.log(`- BUILD: ${params.mainFile}`);
|
||||||
const compileCommand = `javac -cp ${join(params.srcDir, 'src')} ${params.mainFile} -d ${join(params.srcDir, 'build')}`;
|
const compileCommand = `javac -cp ${join(params.srcDir, 'src')} ${params.mainFile} -d ${join(params.srcDir, 'build')}`;
|
||||||
|
|
118
shared/submissionRunner/package-lock.json
generated
Normal file
118
shared/submissionRunner/package-lock.json
generated
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
{
|
||||||
|
"name": "submissionRunner",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
|
"typescript": "^5.4.2",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
|
"@types/node": "20.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/fs-extra": {
|
||||||
|
"version": "11.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
||||||
|
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jsonfile": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/jsonfile": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.11.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz",
|
||||||
|
"integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fs-extra": {
|
||||||
|
"version": "11.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||||
|
},
|
||||||
|
"node_modules/jsonfile": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tree-kill": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||||
|
"bin": {
|
||||||
|
"tree-kill": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||||
|
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "5.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/universalify": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.22.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
||||||
|
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
shared/submissionRunner/package.json
Normal file
15
shared/submissionRunner/package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.x",
|
||||||
|
"@types/fs-extra": "^11.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"typescript": "^5.4.2",
|
||||||
|
"zod": "^3.22.4",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
|
"fs-extra": "^11.2.0"
|
||||||
|
}
|
||||||
|
}
|
1
shared/submissionRunner/settings.cts
Normal file
1
shared/submissionRunner/settings.cts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const timeoutSeconds = 30;
|
14
shared/submissionRunner/tsconfig.json
Normal file
14
shared/submissionRunner/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./build",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
},
|
||||||
|
"include": ["/**/*.cts"]
|
||||||
|
}
|
36
shared/submissionRunner/types.cts
Normal file
36
shared/submissionRunner/types.cts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const RunResultKind = z.enum([
|
||||||
|
'CompileFailed',
|
||||||
|
'TimeLimitExceeded',
|
||||||
|
'Completed',
|
||||||
|
'SandboxError',
|
||||||
|
'RunError'
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type RunResultKind = z.infer<typeof RunResultKind>;
|
||||||
|
|
||||||
|
export const RunResultZod = z
|
||||||
|
.object({
|
||||||
|
kind: RunResultKind,
|
||||||
|
output: z.string().optional(),
|
||||||
|
exitCode: z.number().optional(),
|
||||||
|
runtimeMilliseconds: z.number().optional(),
|
||||||
|
resultKindReason: z.string().optional()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export type RunResult = z.infer<typeof RunResultZod>;
|
||||||
|
|
||||||
|
export interface IRunnerParams {
|
||||||
|
srcDir: string;
|
||||||
|
input: string;
|
||||||
|
outputCallback?: (data: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IRunnerReturn =
|
||||||
|
{ success: true; killFunc: () => void; runResult: Promise<RunResult> } |
|
||||||
|
{ success: false; runResult: RunResult };
|
||||||
|
|
||||||
|
export type IRunner<T extends IRunnerParams = IRunnerParams> =
|
||||||
|
(params: T) => Promise<IRunnerReturn>;
|
@ -1,6 +1,9 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
WORKDIR /app
|
# Setup
|
||||||
|
|
||||||
|
RUN mkdir web
|
||||||
|
WORKDIR /app/web
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install curl -y
|
RUN apt-get install curl -y
|
||||||
@ -11,9 +14,26 @@ RUN apt-get install nodejs git -y
|
|||||||
RUN git config --global user.name "Admin"
|
RUN git config --global user.name "Admin"
|
||||||
RUN git config --global user.email noemail@example.com
|
RUN git config --global user.email noemail@example.com
|
||||||
|
|
||||||
COPY package*.json ./
|
# Prep Web
|
||||||
|
|
||||||
|
COPY ./web/package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY ./web/ .
|
||||||
|
|
||||||
|
# Prep extensionWeb
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir shared
|
||||||
|
RUN mkdir shared/extensionWeb
|
||||||
|
WORKDIR /app/shared/extensionWeb
|
||||||
|
|
||||||
|
COPY ./shared/extensionWeb/package*.json .
|
||||||
|
RUN npm install
|
||||||
|
COPY ./shared/extensionWeb/ .
|
||||||
|
|
||||||
|
# Env/Build/Run
|
||||||
|
|
||||||
|
WORKDIR /app/web
|
||||||
|
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
@ -10,7 +10,9 @@ services:
|
|||||||
- POSTGRES_DB=bwcontest
|
- POSTGRES_DB=bwcontest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
web:
|
web:
|
||||||
build: .
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: ./web/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 7006:7006
|
- 7006:7006
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# THIS FILE MUST USE LF LINE SEPARATORS!
|
||||||
npx prisma db push --accept-data-loss
|
npx prisma db push --accept-data-loss
|
||||||
npx prisma generate
|
npx prisma generate
|
||||||
BODY_SIZE_LIMIT=Infinity INIT=true node build
|
BODY_SIZE_LIMIT=Infinity INIT=true node build
|
@ -1,29 +0,0 @@
|
|||||||
export type FullStateForExtension = {
|
|
||||||
contestState: ContestStateForExtension;
|
|
||||||
submissions: SubmissionForExtension[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ProblemNameForExtension = {
|
|
||||||
id: number;
|
|
||||||
friendlyName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ContestStateForExtension = {
|
|
||||||
startTime: Date | null;
|
|
||||||
endTime: Date | null;
|
|
||||||
problems: ProblemNameForExtension[];
|
|
||||||
isActive: boolean;
|
|
||||||
isScoreboardFrozen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SubmissionStateForExtension = 'Processing' | 'Correct' | 'Incorrect';
|
|
||||||
|
|
||||||
export type SubmissionForExtension = {
|
|
||||||
id: number;
|
|
||||||
contestId: number;
|
|
||||||
teamId: number;
|
|
||||||
problemId: number;
|
|
||||||
createdAt: Date;
|
|
||||||
state: SubmissionStateForExtension;
|
|
||||||
message: string | null;
|
|
||||||
};
|
|
@ -1,5 +1,5 @@
|
|||||||
import type { SubmissionState } from '@prisma/client';
|
import type { SubmissionState } from '@prisma/client';
|
||||||
import type { SubmissionStateForExtension } from './contestMonitorSharedTypes';
|
import type { SubmissionStateForExtension } from '@extensionWeb/contestMonitorTypes.cjs';
|
||||||
|
|
||||||
export function convertSubmissionStateForExtension(
|
export function convertSubmissionStateForExtension(
|
||||||
state: SubmissionState
|
state: SubmissionState
|
||||||
|
@ -5,7 +5,7 @@ import type {
|
|||||||
ContestStateForExtension,
|
ContestStateForExtension,
|
||||||
FullStateForExtension,
|
FullStateForExtension,
|
||||||
SubmissionForExtension
|
SubmissionForExtension
|
||||||
} from '$lib/contestMonitor/contestMonitorSharedTypes';
|
} from '@extensionWeb/contestMonitorTypes.cjs';
|
||||||
import { convertSubmissionStateForExtension } from '$lib/contestMonitor/contestMonitorUtils';
|
import { convertSubmissionStateForExtension } from '$lib/contestMonitor/contestMonitorUtils';
|
||||||
|
|
||||||
export const GET = (async ({ params }) => {
|
export const GET = (async ({ params }) => {
|
||||||
|
@ -3,7 +3,7 @@ import { error, json } from '@sveltejs/kit';
|
|||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { SubmissionState } from '@prisma/client';
|
import { SubmissionState } from '@prisma/client';
|
||||||
import type { SubmissionForExtension } from '$lib/contestMonitor/contestMonitorSharedTypes';
|
import type { SubmissionForExtension } from '@extensionWeb/contestMonitorTypes.cjs';
|
||||||
import { convertSubmissionStateForExtension } from '$lib/contestMonitor/contestMonitorUtils';
|
import { convertSubmissionStateForExtension } from '$lib/contestMonitor/contestMonitorUtils';
|
||||||
|
|
||||||
const submitPostData = z.object({
|
const submitPostData = z.object({
|
||||||
|
@ -6,7 +6,10 @@ const config = {
|
|||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter()
|
adapter: adapter(),
|
||||||
|
alias: {
|
||||||
|
'@extensionWeb/*': '../shared/extensionWeb/*'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
vitePlugin: {
|
vitePlugin: {
|
||||||
inspector: true
|
inspector: true
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
//
|
//
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
Loading…
Reference in New Issue
Block a user