diff --git a/README.md b/README.md index 7300736..4add83a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,24 @@ # Leviathan -Container Orchestrator/Job Runner replacement for Autolab Tango +A container orchestrator and job runner for executing code in docker containers. -## Testing +## Overview + +Leviathan is the code execution engine for the [DevU project](https://github.com/makeopensource/devU). +It securely runs submitted code in isolated containers, providing evaluation results and feedback. +Designed as a modern replacement for [Autolab Tango](https://github.com/autolab/Tango). + +Leviathan maintains full compatibility with existing Autolab/Tango graders. Any graders written for Tango will run on Leviathan without modification. + +## Features +- Secure container-based execution +- Resource limiting and management +- gRPC API for type-safe, efficient communication + +## Documentation +See [docs](docs) for configuration options and other details. + +## Quick Start To test out the dev build, ensure docker is installed on your system @@ -20,7 +36,7 @@ You will need to run 2 services 3. Access the frontend at, http://localhost:3000 -## Running a job +### Running a job You will need to fill out @@ -44,7 +60,7 @@ You will need to fill out ### Example -You can see the test files [here](./example/simple-addition) +You can see the test files [here](example/simple-addition) > [!NOTE] > diff --git a/docs/static/test-example.png b/docs/static/test-example.png new file mode 100644 index 0000000..af16d2e Binary files /dev/null and b/docs/static/test-example.png differ diff --git a/example/tango/tango-Dockerfile b/example/tango/tango-Dockerfile new file mode 100644 index 0000000..86623fc --- /dev/null +++ b/example/tango/tango-Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:24.04 +LABEL org.opencontainers.image.authors="Nicholas Myers" + +RUN apt update + +# C++ Setup +RUN apt install -y gcc +RUN apt install -y make +RUN apt install -y build-essential +RUN apt install -y libcunit1-dev libcunit1-doc libcunit1 + +# Python Setup +RUN apt update --fix-missing +RUN DEBIAN_FRONTEND=nointeractive apt install -y \ + python3 \ + python3-pip \ + python3-numpy \ + python3-pandas \ + python3-nose + +# Java Setup +RUN apt update --fix-missing +RUN apt install -y default-jdk + +# Valgrind Setup +RUN apt update +RUN apt install -y valgrind + +# Utility setup +RUN apt install -y unzip + +# NodeJS setup +RUN apt update --fix-missing +RUN apt install -y nodejs +RUN apt install -y npm + +# Install autodriver +WORKDIR /home +RUN useradd autolab +RUN useradd autograde +RUN mkdir autolab autograde output +RUN chown autolab:autolab autolab +RUN chown autolab:autolab output +RUN chown autograde:autograde autograde +RUN apt update && apt install -y sudo +RUN apt install -y git +RUN git clone https://github.com/autolab/Tango.git +WORKDIR Tango/autodriver +RUN make clean && make +RUN cp autodriver /usr/bin/autodriver +RUN chmod +s /usr/bin/autodriver + +# Clean up +WORKDIR /home +RUN apt -y autoremove +RUN rm -rf Tango/ + +# Check installation +RUN ls -l /home +RUN which autodriver +RUN g++ --version +RUN python3 --version +RUN which javac \ No newline at end of file diff --git a/example/tango/tango0/Makefile b/example/tango/tango0/Makefile new file mode 100644 index 0000000..708cbc6 --- /dev/null +++ b/example/tango/tango0/Makefile @@ -0,0 +1,3 @@ +all: + tar -xf autograde.tar + python3 grader.py \ No newline at end of file diff --git a/example/tango/tango0/autograde.tar b/example/tango/tango0/autograde.tar new file mode 100644 index 0000000..ed3bc04 Binary files /dev/null and b/example/tango/tango0/autograde.tar differ diff --git a/example/tango/tango0/create_grader.sh b/example/tango/tango0/create_grader.sh new file mode 100644 index 0000000..f0db423 --- /dev/null +++ b/example/tango/tango0/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py \ No newline at end of file diff --git a/example/tango/tango0/grader.py b/example/tango/tango0/grader.py new file mode 100644 index 0000000..1123e13 --- /dev/null +++ b/example/tango/tango0/grader.py @@ -0,0 +1,21 @@ +import json + +from handin import get_positive_number + +scores = { + "q1": 0, + "q2": 0, + "q3": 0, +} + +if __name__ == '__main__': + result = get_positive_number() + if result > 0: + scores["q1"] = 10 + scores["q2"] = 10 + scores["q3"] = 10 + print(f"Great job! You returned {result}, which is positive!") + else: + print(f"Try again. You returned {result}, which is not positive.") + + print(json.dumps({"scores": scores})) diff --git a/example/tango/tango0/handin.py b/example/tango/tango0/handin.py new file mode 100644 index 0000000..fb07f23 --- /dev/null +++ b/example/tango/tango0/handin.py @@ -0,0 +1,2 @@ +def get_positive_number() -> int: + return 1 diff --git a/example/tango/tango0/handin_cheating1.py b/example/tango/tango0/handin_cheating1.py new file mode 100644 index 0000000..ca6a69f --- /dev/null +++ b/example/tango/tango0/handin_cheating1.py @@ -0,0 +1,6 @@ +def get_positive_number() -> int: + with open("grader.py", "r") as f: + for line in f.readlines(): + print(line, end="") + + return 0 diff --git a/example/tango/tango0/handin_cheating2.py b/example/tango/tango0/handin_cheating2.py new file mode 100644 index 0000000..8bf069b --- /dev/null +++ b/example/tango/tango0/handin_cheating2.py @@ -0,0 +1,9 @@ +import json +import time + + +def get_positive_number() -> int: + scores = {"scores": {"q1": 10, "q2": 9999, "q3": 9999}} + print(json.dumps(scores), flush=True) + time.sleep(200) + return 0 diff --git a/example/tango/tango0/handin_incorrect1.py b/example/tango/tango0/handin_incorrect1.py new file mode 100644 index 0000000..0e68bcc --- /dev/null +++ b/example/tango/tango0/handin_incorrect1.py @@ -0,0 +1,2 @@ +def get_positive_number() -> int: + return 0 diff --git a/example/tango/tango0/handin_incorrect2.py b/example/tango/tango0/handin_incorrect2.py new file mode 100644 index 0000000..c56d112 --- /dev/null +++ b/example/tango/tango0/handin_incorrect2.py @@ -0,0 +1,2 @@ +def incorrect_function_name() -> int: + return 0 diff --git a/example/tango/tango1/Makefile b/example/tango/tango1/Makefile new file mode 100644 index 0000000..629e3e1 --- /dev/null +++ b/example/tango/tango1/Makefile @@ -0,0 +1,4 @@ +all: + tar -xf autograde.tar + chmod +x grader.sh + ./grader.sh \ No newline at end of file diff --git a/example/tango/tango1/autograde.tar b/example/tango/tango1/autograde.tar new file mode 100644 index 0000000..94fade7 Binary files /dev/null and b/example/tango/tango1/autograde.tar differ diff --git a/example/tango/tango1/create_grader.sh b/example/tango/tango1/create_grader.sh new file mode 100644 index 0000000..9becea4 --- /dev/null +++ b/example/tango/tango1/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py grader.sh \ No newline at end of file diff --git a/example/tango/tango1/grader.py b/example/tango/tango1/grader.py new file mode 100644 index 0000000..d425e66 --- /dev/null +++ b/example/tango/tango1/grader.py @@ -0,0 +1,98 @@ +import json + + +def finished_grading(): + print("\nGrader finished") + print(json.dumps({"scores": scores})) + exit(0) + + +print("Grader starting") + +scores = { + "q1": 0, + "q2": 0, + "q3": 0, +} + +negative_tests = { + -9999987654321: 1, + -99: 1, + -32: 1, + -31: 1, + -30: 1, + -29: 1, + -10: 1, + -3: 1, + -2: 1, + -1: 1 +} + +positive_odd_tests = { + 1: 3, + 3: 5, + 5: 7, + 7: 9, + 9: 11, + 11: 13, + 13: 15, + 15: 17, + 101: 103, + 1005: 1007, +} + +positive_even_tests = { + 2: 3, + 4: 5, + 6: 7, + 8: 9, + 10: 11, + 100: 101, + 1000: 1001, + 12345678: 12345679, + 2222: 2223, + 3336: 3337, +} + +try: + from handin import get_next_positive_odd_number +except ImportError: + print("Unable to import get_next_positive_odd_number from handin.py") + finished_grading() + +print("\nTesting negative numbers") +for input_number, expected_result in negative_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q1"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +print("\nTesting positive odd numbers") +for input_number, expected_result in positive_odd_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q2"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +print("\nTesting positive even numbers") +for input_number, expected_result in positive_even_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q3"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +if all([scores["q1"] == 10, scores["q2"] == 10, scores["q3"] == 10]): + print("\nGreat job!") +else: + print("\nTry again.") + +finished_grading() diff --git a/example/tango/tango1/grader.sh b/example/tango/tango1/grader.sh new file mode 100644 index 0000000..c1aa59e --- /dev/null +++ b/example/tango/tango1/grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python3 grader.py \ No newline at end of file diff --git a/example/tango/tango1/handin.py b/example/tango/tango1/handin.py new file mode 100644 index 0000000..978677d --- /dev/null +++ b/example/tango/tango1/handin.py @@ -0,0 +1,10 @@ +# This is an example of a correct solution. +# It can be helpful to test your grader with the correct solution in the same directory where the student's handin +# will end up. You don't need to include the correct solution in the grader archive. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2) + 1) diff --git a/example/tango/tango1/handin_incorrect1.py b/example/tango/tango1/handin_incorrect1.py new file mode 100644 index 0000000..f44068c --- /dev/null +++ b/example/tango/tango1/handin_incorrect1.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2)) diff --git a/example/tango/tango1/handin_incorrect2.py b/example/tango/tango1/handin_incorrect2.py new file mode 100644 index 0000000..dabbdbd --- /dev/null +++ b/example/tango/tango1/handin_incorrect2.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 3) + 1) diff --git a/example/tango/tango1/handin_incorrect3.py b/example/tango/tango1/handin_incorrect3.py new file mode 100644 index 0000000..995dd7c --- /dev/null +++ b/example/tango/tango1/handin_incorrect3.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return min(1, number + (number % 3) + 1) diff --git a/example/tango/tango1/handin_incorrect4.py b/example/tango/tango1/handin_incorrect4.py new file mode 100644 index 0000000..9ecae22 --- /dev/null +++ b/example/tango/tango1/handin_incorrect4.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution because the function name is incorrect. + +def incorrect_function_name(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2) + 1) diff --git a/example/tango/tango2/Makefile b/example/tango/tango2/Makefile new file mode 100644 index 0000000..708cbc6 --- /dev/null +++ b/example/tango/tango2/Makefile @@ -0,0 +1,3 @@ +all: + tar -xf autograde.tar + python3 grader.py \ No newline at end of file diff --git a/example/tango/tango2/autograde.tar b/example/tango/tango2/autograde.tar new file mode 100644 index 0000000..eb59b27 Binary files /dev/null and b/example/tango/tango2/autograde.tar differ diff --git a/example/tango/tango2/create_grader.sh b/example/tango/tango2/create_grader.sh new file mode 100644 index 0000000..f0db423 --- /dev/null +++ b/example/tango/tango2/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py \ No newline at end of file diff --git a/example/tango/tango2/grader.py b/example/tango/tango2/grader.py new file mode 100644 index 0000000..8866ce1 --- /dev/null +++ b/example/tango/tango2/grader.py @@ -0,0 +1,14 @@ +import json + +scores = { + "q1": 10, + "q2": 10, + "q3": 10, +} + +if __name__ == '__main__': + + with open("settings.json", "r") as f: + print(f.readline()) + + print(json.dumps({"scores": scores})) diff --git a/example/tango/tango3/Makefile b/example/tango/tango3/Makefile new file mode 100644 index 0000000..708cbc6 --- /dev/null +++ b/example/tango/tango3/Makefile @@ -0,0 +1,3 @@ +all: + tar -xf autograde.tar + python3 grader.py \ No newline at end of file diff --git a/example/tango/tango3/autograde.tar b/example/tango/tango3/autograde.tar new file mode 100644 index 0000000..44ecde3 Binary files /dev/null and b/example/tango/tango3/autograde.tar differ diff --git a/example/tango/tango3/create_grader.sh b/example/tango/tango3/create_grader.sh new file mode 100644 index 0000000..f0db423 --- /dev/null +++ b/example/tango/tango3/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py \ No newline at end of file diff --git a/example/tango/tango3/form.html b/example/tango/tango3/form.html new file mode 100644 index 0000000..82ff0f4 --- /dev/null +++ b/example/tango/tango3/form.html @@ -0,0 +1,25 @@ +
+ Type a word that starts with the character 'a': + +
+ +
+ +
+ Were you in lecture today? + + +
+ +
+ +
+ What score would you like for this question? + +
\ No newline at end of file diff --git a/example/tango/tango3/grader.py b/example/tango/tango3/grader.py new file mode 100644 index 0000000..8f5de2e --- /dev/null +++ b/example/tango/tango3/grader.py @@ -0,0 +1,75 @@ +import json + +scores = { + "q1": 0, + "q2": 0, + "q3": 0, +} + + +def finish(): + print() + print(json.dumps({"scores": scores})) + exit(0) + + +if __name__ == '__main__': + + try: + with open("handin.json", "r", encoding="utf-8") as f: + submission = json.load(f) + except Exception: + print("There was a problem parsing your submission.") + finish() + + print("Removing non-ASCII characters from submission.") + + for k, v in submission.items(): + submission[k] = str(v).encode("ascii", "ignore").decode("ascii") + print(f"Your submission: {submission}") + + print("\nGrading question 1") + q1: str = submission.get("q1", None) + print(f"Your answer to question 1 was: {q1}") + if q1 is None: + print("You did not answer question 1.") + else: + if q1.startswith("a"): + scores["q1"] = 10 + print("Good! That starts with the character 'a'.") + elif q1.startswith("A"): + scores["q1"] = 5 + print("Whoops! That starts with the character 'A'. Remember that Python is case-sensitive. Partial credit.") + else: + print("Whoops! That does not start with the character 'a'.") + + print("\nGrading question 2") + q2: str = submission.get("q2", None) + print(f"Your answer to question 2 was: {q2}") + if q2 is None: + print("You did not answer question 2.") + else: + if q2 == "Yes": + scores["q2"] = 10 + print("Good! You confirmed you were in lecture.") + elif q2 == "No": + scores["q2"] = 5 + print("Whoops! You said you were not in lecture. Partial credit for honesty.") + else: + scores["q2"] = 9 + print("You submitted an option that wasn't provided. Partial credit for creativity.") + + print("\nGrading question 3") + q3: str = submission.get("q3", None) + print(f"Your answer to question 3 was: {q3}") + if q3 is None: + print("You did not answer question 3.") + else: + try: + q3_int = int(q3) + print("Good! We set your score for q3 to the number you submitted.") + scores["q3"] = q3_int + except ValueError: + print("Whoops! That's not a number.") + + finish() diff --git a/example/tango/tango3/handin.json b/example/tango/tango3/handin.json new file mode 100644 index 0000000..1867cac --- /dev/null +++ b/example/tango/tango3/handin.json @@ -0,0 +1 @@ +{"utf8":"✓","authenticity_token":"60Na...KIRCA==","submission[embedded_quiz_form_answer]":"","q1":"apple","q2":"Yes","q3":"99","integrity_checkbox":"1"} diff --git a/example/tango/tango4/Makefile b/example/tango/tango4/Makefile new file mode 100644 index 0000000..708cbc6 --- /dev/null +++ b/example/tango/tango4/Makefile @@ -0,0 +1,3 @@ +all: + tar -xf autograde.tar + python3 grader.py \ No newline at end of file diff --git a/example/tango/tango4/autograde.tar b/example/tango/tango4/autograde.tar new file mode 100644 index 0000000..baeb3ff Binary files /dev/null and b/example/tango/tango4/autograde.tar differ diff --git a/example/tango/tango4/create_grader.sh b/example/tango/tango4/create_grader.sh new file mode 100644 index 0000000..d2a8316 --- /dev/null +++ b/example/tango/tango4/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py settings.json \ No newline at end of file diff --git a/example/tango/tango4/form.html b/example/tango/tango4/form.html new file mode 100644 index 0000000..7421d67 --- /dev/null +++ b/example/tango/tango4/form.html @@ -0,0 +1,9 @@ +
+ What string should be returned by the function you wrote? + + +
+ + Upload your code here: + +
\ No newline at end of file diff --git a/example/tango/tango4/form_cse331.html b/example/tango/tango4/form_cse331.html new file mode 100644 index 0000000..4908390 --- /dev/null +++ b/example/tango/tango4/form_cse331.html @@ -0,0 +1,30 @@ +
+

This is a sample embedded form for CSE 331

+ + +
+ + +
+ + +
+ + +
\ No newline at end of file diff --git a/example/tango/tango4/grader.py b/example/tango/tango4/grader.py new file mode 100644 index 0000000..2598896 --- /dev/null +++ b/example/tango/tango4/grader.py @@ -0,0 +1,47 @@ +import json + +scores = { + "q1": 0, +} + + +def finish(): + print() + print(json.dumps({"scores": scores})) + exit(0) + + +if __name__ == '__main__': + + try: + with open("settings.json", "r", encoding="utf-8") as f: + settings = json.load(f) + except Exception as e: + print("There was a problem parsing your form as JSON.") + print(e) + finish() + + print("Removing non-ASCII characters from submission.") + + for k, v in settings.items(): + settings[k] = str(v).encode("ascii", "ignore").decode("ascii") + print(f"Your settings.json: {settings}") + + print("\nGrading question 1") + q1: str = settings.get("q1", None) + print(f"Your input string was: '{q1}'") + print(f"Calling your 'give_me_string' function with argument '{q1}'...") + try: + from handin import give_me_string + + output = give_me_string(q1) + print(f"Your function returned: '{output}'") + if output == q1: + print("Good! Your function returned the same string you passed in.") + scores["q1"] = 10 + else: + print("Whoops! Your function did not return the same string you passed in.") + except Exception as e: + print(f"Your function raised an exception: {e}") + + finish() diff --git a/example/tango/tango4/handin.py b/example/tango/tango4/handin.py new file mode 100644 index 0000000..3585978 --- /dev/null +++ b/example/tango/tango4/handin.py @@ -0,0 +1,2 @@ +def give_me_string(s: str) -> str: + return s diff --git a/example/tango/tango4/handin_incorrect.py b/example/tango/tango4/handin_incorrect.py new file mode 100644 index 0000000..8a1ed00 --- /dev/null +++ b/example/tango/tango4/handin_incorrect.py @@ -0,0 +1,2 @@ +def give_me_string(s: str) -> str: + return s + "_incorrect" diff --git a/example/tango/tango4/settings.json b/example/tango/tango4/settings.json new file mode 100644 index 0000000..4375b6c --- /dev/null +++ b/example/tango/tango4/settings.json @@ -0,0 +1 @@ +{"q1":"This is my string", "info": "This is just a sample file. More fields will exist in the real file."} \ No newline at end of file diff --git a/kraken/index.ts b/kraken/index.ts index 64c23a4..73b1cb2 100644 --- a/kraken/index.ts +++ b/kraken/index.ts @@ -4,10 +4,17 @@ import multer from 'multer'; import { createClient, createConnectTransport, - FileUpload, + DockerFile, JobLogRequest, JobService, - NewJobRequest + LabData, + LabFile, + LabService, + NewJobRequest, + NewLabRequest, + SubmissionFile, + UploadLabFiles, + UploadSubmissionFiles } from "leviathan-node-sdk" import path from "node:path"; @@ -21,6 +28,7 @@ const transport = createConnectTransport({ httpVersion: "2" }); const jobService = createClient(JobService, transport) +const labService = createClient(LabService, transport) const app = express(); const upload = multer(); @@ -35,16 +43,17 @@ app.post('/submit', ]), async (req: Request, res: Response) => { try { - const imageTag = req.body.imageTag as string; const jobTimeout = parseInt(req.body.timeoutInSeconds, 10); if (isNaN(jobTimeout)) { res.status(400).send('Invalid timeout'); return; } - let entryCmd = req.body.entryCmd as string; + let autolabMode = (req.body.autolabCompatibilityMode as String) == "on"; + + let entryCmd = autolabMode ? "" : req.body.entryCmd as string; entryCmd = entryCmd.trim() - if (entryCmd === "" || entryCmd.startsWith("&&") || entryCmd.endsWith("&&")) { + if ((!autolabMode && entryCmd === "") || entryCmd.startsWith("&&") || entryCmd.endsWith("&&")) { res.status(400).send('Invalid entry command must not start or end with && or empty'); return } @@ -59,28 +68,51 @@ app.post('/submit', } const files = req.files as { [fieldname: string]: Express.Multer.File[] }; - const jobFiles = files['fileList'].map(value => { - return { - filename: value.originalname, - content: new Uint8Array(value.buffer), - } + const jobFiles = files['fileList'].map(value => { + fieldName: "labFiles", + filename: value.originalname, + filedata: bufferToBlob(value), }) + if (jobFiles.length < 2) { + res.status(400).send('at least 2 files are required'); + return + } + + const {filename, filedata} = jobFiles.pop()! + const submission = { + fieldName: "submissionFiles", + filename: filename, + filedata: filedata + } + + const dockerfile = files['dockerfile'][0] + const jobFolderID = await UploadLabFiles(leviUrl, { + fieldName: "dockerfile", + filename: dockerfile.filename, + filedata: bufferToBlob(dockerfile), + }, jobFiles) + + const lab = { + labData: { + autolabCompatibilityMode: autolabMode, + entryCmd: entryCmd, + jobTimeoutInSeconds: BigInt(jobTimeout), + limits: { + PidLimit: pids, + CPUCores: cpuCore, + memoryInMb: memory, + }, + labname: "testlab" + }, + tmpFolderId: jobFolderID + } + const labId = await labService.newLab(lab) + const submissionTmpID = await UploadSubmissionFiles(leviUrl, [submission]) const job = { - entryCmd: entryCmd, - jobTimeoutInSeconds: BigInt(jobTimeout), - imageName: imageTag, - limits: { - PidLimit: pids, - CPUCores: cpuCore, - memoryInMb: memory, - }, - jobFiles: jobFiles, - dockerFile: { - content: new Uint8Array(dockerfile.buffer), - filename: dockerfile.originalname, - }, + labID: labId.labId, + tmpSubmissionFolderId: submissionTmpID, } const jobRes = await jobService.newJob(job) @@ -137,3 +169,7 @@ wss.on('connection', async (ws, req) => { console.log("Job ID:", jobId, "done streaming"); }); + +function bufferToBlob(multerFile: Express.Multer.File): Blob { + return new Blob([multerFile.buffer], {type: multerFile.mimetype}); +} \ No newline at end of file diff --git a/kraken/package-lock.json b/kraken/package-lock.json index 16499b4..8d4f676 100644 --- a/kraken/package-lock.json +++ b/kraken/package-lock.json @@ -23,63 +23,38 @@ "form-data": "^4.0.2", "node-fetch": "^3.3.2", "nodemon": "^3.1.9", - "parcel": "^2.13.3", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" } }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@bufbuild/protobuf": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz", - "integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz", + "integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@connectrpc/connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.1.tgz", - "integrity": "sha512-o+MauvcOnfN4vjpS8ngoX+ubhcCDvnexlLwtk/VnkCLjoRMUz2PKqi87Si58ViWq0vzmGYPRf2LNwhJk5kcCXA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.2.tgz", + "integrity": "sha512-xZuylIUNvNlH52e/4eQsZvY4QZyDJRtEFEDnn/yBrv5Xi5ZZI/p8X+GAHH35ucVaBvv9u7OzHZo8+tEh1EFTxA==", "license": "Apache-2.0", "peerDependencies": { "@bufbuild/protobuf": "^2.2.0" } }, "node_modules/@connectrpc/connect-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.0.1.tgz", - "integrity": "sha512-jXE9vMZ71aZnIXPDBqedwQyT/nN5pYs6pI8BScvvxJEKPJNAuwxltdJ2u0RpPaqox/dEW+ipzAN7kjYQIy3NHg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.0.2.tgz", + "integrity": "sha512-33Ut3SRkb6SugpwVCtXXRvUrOdtiyG6z6d5+eijBOLOI75sw1tDCwcs0o/9WL3rUj1M08dLUrPmJB47fjpv6EA==", "license": "Apache-2.0", "engines": { "node": ">=18.14.1" }, "peerDependencies": { "@bufbuild/protobuf": "^2.2.0", - "@connectrpc/connect": "2.0.1" + "@connectrpc/connect": "2.0.2" } }, "node_modules/@cspotcode/source-map-support": { @@ -95,73 +70,61 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", - "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz", - "integrity": "sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], @@ -169,28 +132,33 @@ "license": "MIT", "optional": true, "os": [ - "win32" - ] + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@mischnic/json-sourcemap": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz", - "integrity": "sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0", - "@lezer/lr": "^1.0.0", - "json5": "^2.2.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], @@ -198,1199 +166,322 @@ "license": "MIT", "optional": true, "os": [ - "win32" - ] - }, - "node_modules/@parcel/bundler-default": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.13.3.tgz", - "integrity": "sha512-mOuWeth0bZzRv1b9Lrvydis/hAzJyePy0gwa0tix3/zyYBvw0JY+xkXVR4qKyD/blc1Ra2qOlfI2uD3ucnsdXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/graph": "3.3.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1" - }, + "darwin" + ], "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/cache": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.13.3.tgz", - "integrity": "sha512-Vz5+K5uCt9mcuQAMDo0JdbPYDmVdB8Nvu/A2vTEK2rqZPxvoOTczKeMBA4JqzKqGURHPRLaJCvuR8nDG+jhK9A==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/fs": "2.13.3", - "@parcel/logger": "2.13.3", - "@parcel/utils": "2.13.3", - "lmdb": "2.8.5" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" + "node": ">=18" } }, - "node_modules/@parcel/codeframe": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.13.3.tgz", - "integrity": "sha512-L/PQf+PT0xM8k9nc0B+PxxOYO2phQYnbuifu9o4pFRiqVmCtHztP+XMIvRJ2gOEXy3pgAImSPFVJ3xGxMFky4g==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/compressor-raw": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.13.3.tgz", - "integrity": "sha512-C6vjDlgTLjYc358i7LA/dqcL0XDQZ1IHXFw6hBaHHOfxPKW2T4bzUI6RURyToEK9Q1X7+ggDKqgdLxwp4veCFg==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/config-default": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.13.3.tgz", - "integrity": "sha512-WUsx83ic8DgLwwnL1Bua4lRgQqYjxiTT+DBxESGk1paNm1juWzyfPXEQDLXwiCTcWMQGiXQFQ8OuSISauVQ8dQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/bundler-default": "2.13.3", - "@parcel/compressor-raw": "2.13.3", - "@parcel/namer-default": "2.13.3", - "@parcel/optimizer-css": "2.13.3", - "@parcel/optimizer-htmlnano": "2.13.3", - "@parcel/optimizer-image": "2.13.3", - "@parcel/optimizer-svgo": "2.13.3", - "@parcel/optimizer-swc": "2.13.3", - "@parcel/packager-css": "2.13.3", - "@parcel/packager-html": "2.13.3", - "@parcel/packager-js": "2.13.3", - "@parcel/packager-raw": "2.13.3", - "@parcel/packager-svg": "2.13.3", - "@parcel/packager-wasm": "2.13.3", - "@parcel/reporter-dev-server": "2.13.3", - "@parcel/resolver-default": "2.13.3", - "@parcel/runtime-browser-hmr": "2.13.3", - "@parcel/runtime-js": "2.13.3", - "@parcel/runtime-react-refresh": "2.13.3", - "@parcel/runtime-service-worker": "2.13.3", - "@parcel/transformer-babel": "2.13.3", - "@parcel/transformer-css": "2.13.3", - "@parcel/transformer-html": "2.13.3", - "@parcel/transformer-image": "2.13.3", - "@parcel/transformer-js": "2.13.3", - "@parcel/transformer-json": "2.13.3", - "@parcel/transformer-postcss": "2.13.3", - "@parcel/transformer-posthtml": "2.13.3", - "@parcel/transformer-raw": "2.13.3", - "@parcel/transformer-react-refresh-wrap": "2.13.3", - "@parcel/transformer-svg": "2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@parcel/core": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.13.3.tgz", - "integrity": "sha512-SRZFtqGiaKHlZ2YAvf+NHvBFWS3GnkBvJMfOJM7kxJRK3M1bhbwJa/GgSdzqro5UVf9Bfj6E+pkdrRQIOZ7jMQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/cache": "2.13.3", - "@parcel/diagnostic": "2.13.3", - "@parcel/events": "2.13.3", - "@parcel/feature-flags": "2.13.3", - "@parcel/fs": "2.13.3", - "@parcel/graph": "3.3.3", - "@parcel/logger": "2.13.3", - "@parcel/package-manager": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/profiler": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "@parcel/workers": "2.13.3", - "base-x": "^3.0.8", - "browserslist": "^4.6.6", - "clone": "^2.1.1", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", - "json5": "^2.2.0", - "msgpackr": "^1.9.9", - "nullthrows": "^1.1.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@parcel/diagnostic": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.13.3.tgz", - "integrity": "sha512-C70KXLBaXLJvr7XCEVu8m6TqNdw1gQLxqg5BQ8roR62R4vWWDnOq8PEksxDi4Y8Z/FF4i3Sapv6tRx9iBNxDEg==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@mischnic/json-sourcemap": "^0.1.0", - "nullthrows": "^1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/events": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.13.3.tgz", - "integrity": "sha512-ZkSHTTbD/E+53AjUzhAWTnMLnxLEU5yRw0H614CaruGh+GjgOIKyukGeToF5Gf/lvZ159VrJCGE0Z5EpgHVkuQ==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/feature-flags": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/feature-flags/-/feature-flags-2.13.3.tgz", - "integrity": "sha512-UZm14QpamDFoUut9YtCZSpG1HxPs07lUwUCpsAYL0PpxASD3oWJQxIJGfDZPa2272DarXDG9adTKrNXvkHZblw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/fs": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.13.3.tgz", - "integrity": "sha512-+MPWAt0zr+TCDSlj1LvkORTjfB/BSffsE99A9AvScKytDSYYpY2s0t4vtV9unSh0FHMS2aBCZNJ4t7KL+DcPIg==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/feature-flags": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/types-internal": "2.13.3", - "@parcel/utils": "2.13.3", - "@parcel/watcher": "^2.0.7", - "@parcel/workers": "2.13.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" + "node": ">=18" } }, - "node_modules/@parcel/graph": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.3.3.tgz", - "integrity": "sha512-pxs4GauEdvCN8nRd6wG3st6LvpHske3GfqGwUSR0P0X0pBPI1/NicvXz6xzp3rgb9gPWfbKXeI/2IOTfIxxVfg==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/feature-flags": "2.13.3", - "nullthrows": "^1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/logger": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.13.3.tgz", - "integrity": "sha512-8YF/ZhsQgd7ohQ2vEqcMD1Ag9JlJULROWRPGgGYLGD+twuxAiSdiFBpN3f+j4gQN4PYaLaIS/SwUFx11J243fQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/events": "2.13.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/markdown-ansi": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.13.3.tgz", - "integrity": "sha512-B4rUdlNUulJs2xOQuDbN7Hq5a9roq8IZUcJ1vQ8PAv+zMGb7KCfqIIr/BSCDYGhayfAGBVWW8x55Kvrl1zrDYw==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/namer-default": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.13.3.tgz", - "integrity": "sha512-A2a5A5fuyNcjSGOS0hPcdQmOE2kszZnLIXof7UMGNkNkeC62KAG8WcFZH5RNOY3LT5H773hq51zmc2Y2gE5Rnw==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "nullthrows": "^1.1.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/node-resolver-core": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.4.3.tgz", - "integrity": "sha512-IEnMks49egEic1ITBp59VQyHzkSQUXqpU9hOHwqN3KoSTdZ6rEgrXcS3pa6tdXay4NYGlcZ88kFCE8i/xYoVCg==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/diagnostic": "2.13.3", - "@parcel/fs": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/optimizer-css": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.13.3.tgz", - "integrity": "sha512-A8o9IVCv919vhv69SkLmyW2WjJR5WZgcMqV6L1uiGF8i8z18myrMhrp2JuSHx29PRT9uNyzNC4Xrd4StYjIhJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "browserslist": "^4.6.6", - "lightningcss": "^1.22.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/optimizer-htmlnano": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.13.3.tgz", - "integrity": "sha512-K4Uvg0Sy2pECP7pdvvbud++F0pfcbNkq+IxTrgqBX5HJnLEmRZwgdvZEKF43oMEolclMnURMQRGjRplRaPdbXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "htmlnano": "^2.0.0", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/optimizer-image": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.13.3.tgz", - "integrity": "sha512-wlDUICA29J4UnqkKrWiyt68g1e85qfYhp4zJFcFJL0LX1qqh1QwsLUz3YJ+KlruoqPxJSFEC8ncBEKiVCsqhEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/utils": "2.13.3", - "@parcel/workers": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" - } - }, - "node_modules/@parcel/optimizer-svgo": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.13.3.tgz", - "integrity": "sha512-piIKxQKzhZK54dJR6yqIcq+urZmpsfgUpLCZT3cnWlX4ux5+S2iN66qqZBs0zVn+a58LcWcoP4Z9ieiJmpiu2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/optimizer-swc": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.13.3.tgz", - "integrity": "sha512-zNSq6oWqLlW8ksPIDjM0VgrK6ZAJbPQCDvs1V+p0oX3CzEe85lT5VkRpnfrN1+/vvEJNGL8e60efHKpI+rXGTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "@swc/core": "^1.7.26", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/package-manager": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.13.3.tgz", - "integrity": "sha512-FLNI5OrZxymGf/Yln0E/kjnGn5sdkQAxW7pQVdtuM+5VeN75yibJRjsSGv88PvJ+KvpD2ANgiIJo1RufmoPcww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/fs": "2.13.3", - "@parcel/logger": "2.13.3", - "@parcel/node-resolver-core": "3.4.3", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "@parcel/workers": "2.13.3", - "@swc/core": "^1.7.26", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" - } - }, - "node_modules/@parcel/packager-css": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.13.3.tgz", - "integrity": "sha512-ghDqRMtrUwaDERzFm9le0uz2PTeqqsjsW0ihQSZPSAptElRl9o5BR+XtMPv3r7Ui0evo+w35gD55oQCJ28vCig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "lightningcss": "^1.22.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-html": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.13.3.tgz", - "integrity": "sha512-jDLnKSA/EzVEZ3/aegXO3QJ/Ij732AgBBkIQfeC8tUoxwVz5b3HiPBAjVjcUSfZs7mdBSHO+ELWC3UD+HbsIrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-js": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.13.3.tgz", - "integrity": "sha512-0pMHHf2zOn7EOJe88QJw5h/wcV1bFfj6cXVcE55Wa8GX3V+SdCgolnlvNuBcRQ1Tlx0Xkpo+9hMFVIQbNQY6zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "globals": "^13.2.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-raw": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.13.3.tgz", - "integrity": "sha512-AWu4UB+akBdskzvT3KGVHIdacU9f7cI678DQQ1jKQuc9yZz5D0VFt3ocFBOmvDfEQDF0uH3jjtJR7fnuvX7Biw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-svg": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.13.3.tgz", - "integrity": "sha512-tKGRiFq/4jh5u2xpTstNQ7gu+RuZWzlWqpw5NaFmcKe6VQe5CMcS499xTFoREAGnRvevSeIgC38X1a+VOo+/AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "posthtml": "^0.16.4" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/packager-wasm": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.13.3.tgz", - "integrity": "sha512-SZB56/b230vFrSehVXaUAWjJmWYc89gzb8OTLkBm7uvtFtov2J1R8Ig9TTJwinyXE3h84MCFP/YpQElSfoLkJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3" - }, - "engines": { - "node": ">=16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/plugin": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.13.3.tgz", - "integrity": "sha512-cterKHHcwg6q11Gpif/aqvHo056TR+yDVJ3fSdiG2xr5KD1VZ2B3hmofWERNNwjMcnR1h9Xq40B7jCKUhOyNFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/types": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/profiler": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.13.3.tgz", - "integrity": "sha512-ok6BwWSLvyHe5TuSXjSacYnDStFgP5Y30tA9mbtWSm0INDsYf+m5DqzpYPx8U54OaywWMK8w3MXUClosJX3aPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/events": "2.13.3", - "@parcel/types-internal": "2.13.3", - "chrome-trace-event": "^1.0.2" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/reporter-cli": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.13.3.tgz", - "integrity": "sha512-EA5tKt/6bXYNMEavSs35qHlFdx6cZmRazlZxPBgxPePQYoouNAPMNLUOEQozaPhz9f5fvNDN7EHOFaAWcdO2LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/types": "2.13.3", - "@parcel/utils": "2.13.3", - "chalk": "^4.1.2", - "term-size": "^2.2.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/reporter-dev-server": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.13.3.tgz", - "integrity": "sha512-ZNeFp6AOIQFv7mZIv2P5O188dnZHNg0ymeDVcakfZomwhpSva2dFNS3AnvWo4eyWBlUxkmQO8BtaxeWTs7jAuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/reporter-tracer": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.13.3.tgz", - "integrity": "sha512-aBsVPI8jLZTDkFYrI69GxnsdvZKEYerkPsu935LcX9rfUYssOnmmUP+3oI+8fbg+qNjJuk9BgoQ4hCp9FOphMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "chrome-trace-event": "^1.0.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/resolver-default": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.13.3.tgz", - "integrity": "sha512-urBZuRALWT9pFMeWQ8JirchLmsQEyI9lrJptiwLbJWrwvmlwSUGkcstmPwoNRf/aAQjICB7ser/247Vny0pFxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/node-resolver-core": "3.4.3", - "@parcel/plugin": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/runtime-browser-hmr": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.13.3.tgz", - "integrity": "sha512-EAcPojQFUNUGUrDk66cu3ySPO0NXRVS5CKPd4QrxPCVVbGzde4koKu8krC/TaGsoyUqhie8HMnS70qBP0GFfcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/runtime-js": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.13.3.tgz", - "integrity": "sha512-62OucNAnxb2Q0uyTFWW/0Hvv2DJ4b5H6neh/YFu2/wmxaZ37xTpEuEcG2do7KW54xE5DeLP+RliHLwi4NvR3ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/runtime-react-refresh": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.13.3.tgz", - "integrity": "sha512-PYZ1klpJVwqE3WuifILjtF1dugtesHEuJcXYZI85T6UoRSD5ctS1nAIpZzT14Ga1lRt/jd+eAmhWL1l3m/Vk1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "react-error-overlay": "6.0.9", - "react-refresh": ">=0.9 <=0.14" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/runtime-service-worker": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.13.3.tgz", - "integrity": "sha512-BjMhPuT7Us1+YIo31exPRwomPiL+jrZZS5UUAwlEW2XGHDceEotzRM94LwxeFliCScT4IOokGoxixm19qRuzWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/rust": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.13.3.tgz", - "integrity": "sha512-dLq85xDAtzr3P5200cvxk+8WXSWauYbxuev9LCPdwfhlaWo/JEj6cu9seVdWlkagjGwkoV1kXC+GGntgUXOLAQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/source-map": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", - "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^1.0.3" - }, - "engines": { - "node": "^12.18.3 || >=14" - } - }, - "node_modules/@parcel/transformer-babel": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.13.3.tgz", - "integrity": "sha512-ikzK9f5WTFrdQsPitQgjCPH6HmVU8AQPRemIJ2BndYhtodn5PQut5cnSvTrqax8RjYvheEKCQk/Zb/uR7qgS3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "browserslist": "^4.6.6", - "json5": "^2.2.0", - "nullthrows": "^1.1.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-css": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.13.3.tgz", - "integrity": "sha512-zbrNURGph6JeVADbGydyZ7lcu/izj41kDxQ9xw4RPRW/3rofQiTU0OTREi+uBWiMENQySXVivEdzHA9cA+aLAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "browserslist": "^4.6.6", - "lightningcss": "^1.22.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-html": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.13.3.tgz", - "integrity": "sha512-Yf74FkL9RCCB4+hxQRVMNQThH9+fZ5w0NLiQPpWUOcgDEEyxTi4FWPQgEBsKl/XK2ehdydbQB9fBgPQLuQxwPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.12.1", - "posthtml-render": "^3.0.0", - "semver": "^7.5.2", - "srcset": "4" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-html/node_modules/srcset": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@parcel/transformer-image": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.13.3.tgz", - "integrity": "sha512-wL1CXyeFAqbp2wcEq/JD3a/tbAyVIDMTC6laQxlIwnVV7dsENhK1qRuJZuoBdixESeUpFQSmmQvDIhcfT/cUUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "@parcel/workers": "2.13.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" - } - }, - "node_modules/@parcel/transformer-js": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.13.3.tgz", - "integrity": "sha512-KqfNGn1IHzDoN2aPqt4nDksgb50Xzcny777C7A7hjlQ3cmkjyJrixYjzzsPaPSGJ+kJpknh3KE8unkQ9mhFvRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.13.3", - "@parcel/workers": "2.13.3", - "@swc/helpers": "^0.5.0", - "browserslist": "^4.6.6", - "nullthrows": "^1.1.1", - "regenerator-runtime": "^0.14.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" - } - }, - "node_modules/@parcel/transformer-json": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.13.3.tgz", - "integrity": "sha512-rrq0ab6J0w9ePtsxi0kAvpCmrUYXXAx1Z5PATZakv89rSYbHBKEdXxyCoKFui/UPVCUEGVs5r0iOFepdHpIyeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "json5": "^2.2.0" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-postcss": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.13.3.tgz", - "integrity": "sha512-AIiWpU0QSFBrPcYIqAnhqB8RGE6yHFznnxztfg1t2zMSOnK3xoU6xqYKv8H/MduShGGrC3qVOeDfM8MUwzL3cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/utils": "2.13.3", - "clone": "^2.1.1", - "nullthrows": "^1.1.1", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-posthtml": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.13.3.tgz", - "integrity": "sha512-5GSLyccpHASwFAu3uJ83gDIBSvfsGdVmhJvy0Vxe+K1Fklk2ibhvvtUHMhB7mg6SPHC+R9jsNc3ZqY04ZLeGjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.12.1", - "posthtml-render": "^3.0.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-raw": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.13.3.tgz", - "integrity": "sha512-BFsAbdQF0l8/Pdb7dSLJeYcd8jgwvAUbHgMink2MNXJuRUvDl19Gns8jVokU+uraFHulJMBj40+K/RTd33in4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-react-refresh-wrap": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.13.3.tgz", - "integrity": "sha512-mOof4cRyxsZRdg8kkWaFtaX98mHpxUhcGPU+nF9RQVa9q737ItxrorsPNR9hpZAyE2TtFNflNW7RoYsgvlLw8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.13.3", - "@parcel/utils": "2.13.3", - "react-refresh": ">=0.9 <=0.14" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/transformer-svg": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.13.3.tgz", - "integrity": "sha512-9jm7ZF4KHIrGLWlw/SFUz5KKJ20nxHvjFAmzde34R9Wu+F1BOjLZxae7w4ZRwvIc+UVOUcBBQFmhSVwVDZg6Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/plugin": "2.13.3", - "@parcel/rust": "2.13.3", - "nullthrows": "^1.1.1", - "posthtml": "^0.16.5", - "posthtml-parser": "^0.12.1", - "posthtml-render": "^3.0.0", - "semver": "^7.5.2" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 16.0.0", - "parcel": "^2.13.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/types": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.13.3.tgz", - "integrity": "sha512-+RpFHxx8fy8/dpuehHUw/ja9PRExC3wJoIlIIF42E7SLu2SvlTHtKm6EfICZzxCXNEBzjoDbamCRcN0nmTPlhw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/types-internal": "2.13.3", - "@parcel/workers": "2.13.3" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@parcel/types-internal": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/types-internal/-/types-internal-2.13.3.tgz", - "integrity": "sha512-Lhx0n+9RCp+Ipktf/I+CLm3zE9Iq9NtDd8b2Vr5lVWyoT8AbzBKIHIpTbhLS4kjZ80L3I6o93OYjqAaIjsqoZw==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/feature-flags": "2.13.3", - "@parcel/source-map": "^2.1.1", - "utility-types": "^3.10.0" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@parcel/utils": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.13.3.tgz", - "integrity": "sha512-yxY9xw2wOUlJaScOXYZmMGoZ4Ck4Kqj+p6Koe5kLkkWM1j98Q0Dj2tf/mNvZi4yrdnlm+dclCwNRnuE8Q9D+pw==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@parcel/codeframe": "2.13.3", - "@parcel/diagnostic": "2.13.3", - "@parcel/logger": "2.13.3", - "@parcel/markdown-ansi": "2.13.3", - "@parcel/rust": "2.13.3", - "@parcel/source-map": "^2.1.1", - "chalk": "^4.1.2", - "nullthrows": "^1.1.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], @@ -1401,36 +492,53 @@ "win32" ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=18" } }, - "node_modules/@parcel/workers": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.13.3.tgz", - "integrity": "sha512-oAHmdniWTRwwwsKbcF4t3VjOtKN+/W17Wj5laiYB+HLkfsjGTfIQPj3sdXmrlBAGpI4omIcvR70PHHXnfdTfwA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@parcel/diagnostic": "2.13.3", - "@parcel/logger": "2.13.3", - "@parcel/profiler": "2.13.3", - "@parcel/types-internal": "2.13.3", - "@parcel/utils": "2.13.3", - "nullthrows": "^1.1.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "peerDependencies": { - "@parcel/core": "^2.13.3" + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@swc/core": { @@ -1440,6 +548,8 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.17" @@ -1485,6 +595,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1494,7 +605,9 @@ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/@swc/helpers": { "version": "0.5.15", @@ -1502,6 +615,8 @@ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1512,6 +627,8 @@ "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -1788,13 +905,6 @@ "dev": true, "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1815,16 +925,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base-x": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", - "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1885,39 +985,6 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1973,54 +1040,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2046,26 +1065,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2099,16 +1098,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2173,33 +1162,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2261,124 +1223,23 @@ } }, "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, - "license": "BSD-2-Clause", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">=0.3.1" } }, "node_modules/dunder-proto": { @@ -2408,13 +1269,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.102", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", - "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", - "dev": true, - "license": "ISC" - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2431,39 +1285,6 @@ "node": ">= 0.8" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2510,14 +1331,45 @@ "node": ">= 0.4" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/escape-html": { @@ -2748,16 +1600,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-port": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", - "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -2771,6 +1613,19 @@ "node": ">= 0.4" } }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", @@ -2808,22 +1663,6 @@ "node": ">= 6" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2836,16 +1675,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2886,74 +1715,6 @@ "node": ">= 0.4" } }, - "node_modules/htmlnano": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", - "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cosmiconfig": "^9.0.0", - "posthtml": "^0.16.5", - "timsort": "^0.3.0" - }, - "peerDependencies": { - "cssnano": "^7.0.0", - "postcss": "^8.3.11", - "purgecss": "^6.0.0", - "relateurl": "^0.2.7", - "srcset": "5.0.1", - "svgo": "^3.0.2", - "terser": "^5.10.0", - "uncss": "^0.17.3" - }, - "peerDependenciesMeta": { - "cssnano": { - "optional": true - }, - "postcss": { - "optional": true - }, - "purgecss": { - "optional": true - }, - "relateurl": { - "optional": true - }, - "srcset": { - "optional": true - }, - "svgo": { - "optional": true - }, - "terser": { - "optional": true - }, - "uncss": { - "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2989,23 +1750,6 @@ "dev": true, "license": "ISC" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3021,13 +1765,6 @@ "node": ">= 0.10" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3074,13 +1811,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "dev": true, - "license": "ISC" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3120,50 +1850,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/leviathan-node-sdk": { "version": "1.0.0", "resolved": "https://gitpkg.vercel.app/makeopensource/leviathan/spec/leviathan_node?dev", - "integrity": "sha512-79uuqbLMXJ/O8euZy+dbMpPyqV//Bao1Hiyja1Dc9kmrvxxn9RDAdA4rXNyhzkiIzYYyaiAghUgYcF452uONmw==", + "integrity": "sha512-wyYdYGcmRqXSWdHPTegBRXt2pE9czkJkdD5l9/3eZIEvcn7iNb0TYwmneU3Vs3krH/uf2nrLKBoDXqTqad+1Qg==", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -3176,7 +1866,7 @@ "node_modules/leviathan-node-sdk/node_modules/leviathan-node-sdk": { "version": "1.0.0", "resolved": "https://gitpkg.vercel.app/makeopensource/leviathan/spec/leviathan_node?master", - "integrity": "sha512-STXad6Crv/O+H5mcOkLsr/62a4KgzshC+0jysYpRKLr4Efn1gfrCkPuLSfF4T+7mP/0zkndAdl+uCOOXQmTTxg==", + "integrity": "sha512-A2dNWIDNGlTIMllmINvn8z0J3/Ga1ZCaqudXvlS+L0pH8uCekPyimC1E//eIZmMX90e12ZoilTQRUGBCw6cH4g==", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -3186,96 +1876,6 @@ "leviathan-node-sdk": "https://gitpkg.vercel.app/makeopensource/leviathan/spec/leviathan_node?master" } }, - "node_modules/lightningcss": { - "version": "1.29.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz", - "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^1.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.29.1", - "lightningcss-darwin-x64": "1.29.1", - "lightningcss-freebsd-x64": "1.29.1", - "lightningcss-linux-arm-gnueabihf": "1.29.1", - "lightningcss-linux-arm64-gnu": "1.29.1", - "lightningcss-linux-arm64-musl": "1.29.1", - "lightningcss-linux-x64-gnu": "1.29.1", - "lightningcss-linux-x64-musl": "1.29.1", - "lightningcss-win32-arm64-msvc": "1.29.1", - "lightningcss-win32-x64-msvc": "1.29.1" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.29.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz", - "integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lmdb": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz", - "integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "msgpackr": "^1.9.5", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.1.1", - "ordered-binary": "^1.4.1", - "weak-lru-cache": "^1.2.2" - }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "2.8.5", - "@lmdb/lmdb-darwin-x64": "2.8.5", - "@lmdb/lmdb-linux-arm": "2.8.5", - "@lmdb/lmdb-linux-arm64": "2.8.5", - "@lmdb/lmdb-linux-x64": "2.8.5", - "@lmdb/lmdb-win32-x64": "2.8.5" - } - }, - "node_modules/lmdb/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -3329,20 +1929,6 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3392,103 +1978,43 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/msgpackr-extract/node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "Apache-2.0", - "optional": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "dev": true, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", - "optional": true, "dependencies": { - "detect-libc": "^2.0.1" + "minimist": "^1.2.6" }, "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "mkdirp": "bin/cmd.js" } }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/multer": { "version": "1.4.5-lts.1", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", @@ -3516,13 +2042,6 @@ "node": ">= 0.6" } }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT" - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -3562,38 +2081,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, "node_modules/nodemon": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", @@ -3705,13 +2192,6 @@ "node": ">=0.10.0" } }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3745,13 +2225,6 @@ "node": ">= 0.8" } }, - "node_modules/ordered-binary": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", - "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", - "dev": true, - "license": "MIT" - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3759,72 +2232,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/parcel": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.13.3.tgz", - "integrity": "sha512-8GrC8C7J8mwRpAlk7EJ7lwdFTbCN+dcXH2gy5AsEs9pLfzo9wvxOTx6W0fzSlvCOvZOita+8GdfYlGfEt0tRgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/config-default": "2.13.3", - "@parcel/core": "2.13.3", - "@parcel/diagnostic": "2.13.3", - "@parcel/events": "2.13.3", - "@parcel/feature-flags": "2.13.3", - "@parcel/fs": "2.13.3", - "@parcel/logger": "2.13.3", - "@parcel/package-manager": "2.13.3", - "@parcel/reporter-cli": "2.13.3", - "@parcel/reporter-dev-server": "2.13.3", - "@parcel/reporter-tracer": "2.13.3", - "@parcel/utils": "2.13.3", - "chalk": "^4.1.2", - "commander": "^12.1.0", - "get-port": "^4.2.0" - }, - "bin": { - "parcel": "lib/bin.js" - }, - "engines": { - "node": ">= 16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3867,13 +2274,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3887,155 +2287,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/posthtml-parser": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.12.1.tgz", - "integrity": "sha512-rYFmsDLfYm+4Ts2Oh4DCDSZPtdC1BLnRXAobypVzX9alj28KGl65dIFtgDY9zB57D0TC4Qxqrawuq/2et1P0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "htmlparser2": "^9.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-json": "^2.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/posthtml/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/posthtml/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/posthtml/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/posthtml/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/posthtml/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/posthtml/node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "node_modules/posthtml/node_modules/posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "htmlparser2": "^7.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -4101,23 +2352,6 @@ "node": ">= 0.8" } }, - "node_modules/react-error-overlay": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", - "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -4152,21 +2386,14 @@ "node": ">=8.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/rimraf": { @@ -4409,21 +2636,6 @@ "node": ">=10" } }, - "node_modules/srcset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz", - "integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4560,39 +2772,6 @@ "node": ">=8" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", - "dev": true, - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4674,19 +2853,28 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "optional": true, + "peer": true }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/tsx": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, "engines": { - "node": ">=10" + "node": ">=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "~2.3.3" } }, "node_modules/type-is": { @@ -4745,53 +2933,12 @@ "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -4817,13 +2964,6 @@ "node": ">= 0.8" } }, - "node_modules/weak-lru-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "dev": true, - "license": "MIT" - }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/kraken/package.json b/kraken/package.json index 700e5ef..72af996 100644 --- a/kraken/package.json +++ b/kraken/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "scripts": { - "start": "ts-node index.ts", + "start": "tsx --watch index.ts", "watch": "npm run build && nodemon index.ts", "sta": "ts-node index.ts", "build": "parcel build ui/* --dist-dir ui/dist", @@ -32,6 +32,7 @@ "nodemon": "^3.1.9", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.3", "typescript": "^5.7.3" } } diff --git a/kraken/ui/index.html b/kraken/ui/index.html index 9534def..454018d 100644 --- a/kraken/ui/index.html +++ b/kraken/ui/index.html @@ -1,159 +1,341 @@ - + - Leviathan + Leviathan Test -

Leviathan test

-
- - -
- - -
-
    -
  • Upload all files required for the job, (includes the student submission, grader script and any other supporting files)
  • -
  • Makefiles are optional, you can use any script you prefer as long as it can be called in the dockerfile
  • -
  • you must specify the entry command below, for the job to start, leviathan does not call any entry command by default
  • -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - - - - - - - -
+
+

Leviathan test

+
+ +
+ + +
+
    +
  • Upload all files required for the job, (includes the student submission, grader script and any other supporting files)
  • +
  • Makefiles are optional, you can use any script you prefer as long as it can be called in the dockerfile
  • +
  • you must specify the entry command below, for the job to start, leviathan does not call any entry command by default
  • +
+
+ + +
+ + +
+ + +
+ +
+ ? + Enable this mode for compatibility with Autolab submission systems. When enabled, files will be formatted to meet Autolab requirements. +
+
+ + +
+ + +
+ + +
+ + +
+ + + + + + + + + + +
+
- + \ No newline at end of file diff --git a/spec/leviathan_node/package-lock.json b/spec/leviathan_node/package-lock.json index 03ce388..bc120af 100644 --- a/spec/leviathan_node/package-lock.json +++ b/spec/leviathan_node/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@types/node": "^20.0.0", "rimraf": "^6.0.1", + "ts-node": "^10.9.2", "typescript": "^5.0.0" } }, @@ -49,6 +50,19 @@ "@connectrpc/connect": "2.0.1" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -67,6 +81,62 @@ "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.19.tgz", @@ -77,6 +147,32 @@ "undici-types": "~6.19.2" } }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -103,6 +199,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -140,6 +243,13 @@ "dev": true, "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -155,6 +265,16 @@ "node": ">= 8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -264,6 +384,13 @@ "node": "20 || >=22" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -484,6 +611,50 @@ "node": ">=8" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -505,6 +676,13 @@ "dev": true, "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -618,6 +796,16 @@ "engines": { "node": ">=8" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } } } } diff --git a/spec/leviathan_node/package.json b/spec/leviathan_node/package.json index 66a0d8f..02b55ce 100644 --- a/spec/leviathan_node/package.json +++ b/spec/leviathan_node/package.json @@ -8,6 +8,7 @@ "license": "ISC", "description": "node gRPC stubs for the leviathan service", "scripts": { + "test": "npx ts-node src/index_test.ts", "build": "tsc", "postinstall": "npm run build", "clean": "npx rimraf node_modules dist" @@ -19,6 +20,7 @@ "leviathan-node-sdk": "https://gitpkg.vercel.app/makeopensource/leviathan/spec/leviathan_node?master" }, "devDependencies": { + "ts-node": "^10.9.2", "@types/node": "^20.0.0", "rimraf": "^6.0.1", "typescript": "^5.0.0" diff --git a/spec/leviathan_node/src/generated/docker_rpc/v1/docker_pb.ts b/spec/leviathan_node/src/generated/docker_rpc/v1/docker_pb.ts index 80d2464..0fbd284 100644 --- a/spec/leviathan_node/src/generated/docker_rpc/v1/docker_pb.ts +++ b/spec/leviathan_node/src/generated/docker_rpc/v1/docker_pb.ts @@ -4,7 +4,6 @@ import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; -import type { FileUpload } from "../../types/v1/types_pb"; import { file_types_v1_types } from "../../types/v1/types_pb"; import type { Message } from "@bufbuild/protobuf"; @@ -12,7 +11,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file docker_rpc/v1/docker.proto. */ export const file_docker_rpc_v1_docker: GenFile = /*@__PURE__*/ - fileDesc("Chpkb2NrZXJfcnBjL3YxL2RvY2tlci5wcm90bxINZG9ja2VyX3JwYy52MSIrChVTdGFydENvbnRhaW5lclJlcXVlc3QSEgoKY29tYmluZWRJZBgBIAEoCSIYChZTdGFydENvbnRhaW5lclJlc3BvbnNlIioKFFN0b3BDb250YWluZXJSZXF1ZXN0EhIKCmNvbWJpbmVkSWQYASABKAkiFwoVU3RvcENvbnRhaW5lclJlc3BvbnNlIiwKFkdldENvbnRhaW5lckxvZ1JlcXVlc3QSEgoKY29tYmluZWRJZBgBIAEoCSInChdHZXRDb250YWluZXJMb2dSZXNwb25zZRIMCgRsb2dzGAEgASgJIj0KFkNyZWF0ZUNvbnRhaW5lclJlcXVlc3QSEAoIaW1hZ2VUYWcYASABKAkSEQoJbWFjaGluZUlEGAIgASgJIi4KF0NyZWF0ZUNvbnRhaW5lclJlc3BvbnNlEhMKC2NvbnRhaW5lcklkGAEgASgJIi0KFkRlbGV0ZUNvbnRhaW5lclJlcXVlc3QSEwoLY29udGFpbmVySWQYASABKAkiGQoXRGVsZXRlQ29udGFpbmVyUmVzcG9uc2UiFwoVTGlzdENvbnRhaW5lcnNSZXF1ZXN0IkwKFkxpc3RDb250YWluZXJzUmVzcG9uc2USMgoKY29udGFpbmVycxgBIAMoCzIeLmRvY2tlcl9ycGMudjEuRG9ja2VyQ29udGFpbmVyIhIKEE5ld0ltYWdlUmVzcG9uc2UiRwoPTmV3SW1hZ2VSZXF1ZXN0EhAKCGltYWdlVGFnGAEgASgJEiIKBGZpbGUYAiABKAsyFC50eXBlcy52MS5GaWxlVXBsb2FkIj8KEUxpc3RJbWFnZVJlc3BvbnNlEioKBmltYWdlcxgBIAMoCzIaLmRvY2tlcl9ycGMudjEuRG9ja2VySW1hZ2UiEgoQTGlzdEltYWdlUmVxdWVzdCJRCg9Eb2NrZXJDb250YWluZXISCgoCaWQYASABKAkSMgoIbWV0YWRhdGEYAiADKAsyIC5kb2NrZXJfcnBjLnYxLkNvbnRhaW5lck1ldGFEYXRhImUKEUNvbnRhaW5lck1ldGFEYXRhEgoKAklkGAEgASgJEhYKDkNvbnRhaW5lck5hbWVzGAIgAygJEg0KBUltYWdlGAQgASgJEg4KBlN0YXR1cxgDIAEoCRINCgVTdGF0ZRgGIAEoCSJJCgtEb2NrZXJJbWFnZRIKCgJpZBgBIAEoCRIuCghtZXRhZGF0YRgCIAMoCzIcLmRvY2tlcl9ycGMudjEuSW1hZ2VNZXRhRGF0YSJOCg1JbWFnZU1ldGFEYXRhEgoKAklkGAEgASgJEhAKCFJlcG9UYWdzGAIgAygJEgwKBFNpemUYAyABKAMSEQoJQ3JlYXRlZEF0GAQgASgDMoYGCg1Eb2NrZXJTZXJ2aWNlEmIKD0NyZWF0ZUNvbnRhaW5lchIlLmRvY2tlcl9ycGMudjEuQ3JlYXRlQ29udGFpbmVyUmVxdWVzdBomLmRvY2tlcl9ycGMudjEuQ3JlYXRlQ29udGFpbmVyUmVzcG9uc2UiABJiCg9EZWxldGVDb250YWluZXISJS5kb2NrZXJfcnBjLnYxLkRlbGV0ZUNvbnRhaW5lclJlcXVlc3QaJi5kb2NrZXJfcnBjLnYxLkRlbGV0ZUNvbnRhaW5lclJlc3BvbnNlIgASXwoOTGlzdENvbnRhaW5lcnMSJC5kb2NrZXJfcnBjLnYxLkxpc3RDb250YWluZXJzUmVxdWVzdBolLmRvY2tlcl9ycGMudjEuTGlzdENvbnRhaW5lcnNSZXNwb25zZSIAEl8KDlN0YXJ0Q29udGFpbmVyEiQuZG9ja2VyX3JwYy52MS5TdGFydENvbnRhaW5lclJlcXVlc3QaJS5kb2NrZXJfcnBjLnYxLlN0YXJ0Q29udGFpbmVyUmVzcG9uc2UiABJcCg1TdG9wQ29udGFpbmVyEiMuZG9ja2VyX3JwYy52MS5TdG9wQ29udGFpbmVyUmVxdWVzdBokLmRvY2tlcl9ycGMudjEuU3RvcENvbnRhaW5lclJlc3BvbnNlIgASZQoQR2V0Q29udGFpbmVyTG9ncxIlLmRvY2tlcl9ycGMudjEuR2V0Q29udGFpbmVyTG9nUmVxdWVzdBomLmRvY2tlcl9ycGMudjEuR2V0Q29udGFpbmVyTG9nUmVzcG9uc2UiADABElMKDkNyZWF0ZU5ld0ltYWdlEh4uZG9ja2VyX3JwYy52MS5OZXdJbWFnZVJlcXVlc3QaHy5kb2NrZXJfcnBjLnYxLk5ld0ltYWdlUmVzcG9uc2UiABJRCgpMaXN0SW1hZ2VzEh8uZG9ja2VyX3JwYy52MS5MaXN0SW1hZ2VSZXF1ZXN0GiAuZG9ja2VyX3JwYy52MS5MaXN0SW1hZ2VSZXNwb25zZSIAQq4BChFjb20uZG9ja2VyX3JwYy52MUILRG9ja2VyUHJvdG9QAVo7Z2l0aHViLmNvbS9tYWtlb3BlbnNvdXJjZS9sZXZpYXRoYW4vZ2VuZXJhdGVkL2RvY2tlcl9ycGMvdjGiAgNEWFiqAgxEb2NrZXJScGMuVjHKAgxEb2NrZXJScGNcVjHiAhhEb2NrZXJScGNcVjFcR1BCTWV0YWRhdGHqAg1Eb2NrZXJScGM6OlYxYgZwcm90bzM", [file_types_v1_types]); + fileDesc("Chpkb2NrZXJfcnBjL3YxL2RvY2tlci5wcm90bxINZG9ja2VyX3JwYy52MSIrChVTdGFydENvbnRhaW5lclJlcXVlc3QSEgoKY29tYmluZWRJZBgBIAEoCSIYChZTdGFydENvbnRhaW5lclJlc3BvbnNlIioKFFN0b3BDb250YWluZXJSZXF1ZXN0EhIKCmNvbWJpbmVkSWQYASABKAkiFwoVU3RvcENvbnRhaW5lclJlc3BvbnNlIiwKFkdldENvbnRhaW5lckxvZ1JlcXVlc3QSEgoKY29tYmluZWRJZBgBIAEoCSInChdHZXRDb250YWluZXJMb2dSZXNwb25zZRIMCgRsb2dzGAEgASgJIj0KFkNyZWF0ZUNvbnRhaW5lclJlcXVlc3QSEAoIaW1hZ2VUYWcYASABKAkSEQoJbWFjaGluZUlEGAIgASgJIi4KF0NyZWF0ZUNvbnRhaW5lclJlc3BvbnNlEhMKC2NvbnRhaW5lcklkGAEgASgJIi0KFkRlbGV0ZUNvbnRhaW5lclJlcXVlc3QSEwoLY29udGFpbmVySWQYASABKAkiGQoXRGVsZXRlQ29udGFpbmVyUmVzcG9uc2UiFwoVTGlzdENvbnRhaW5lcnNSZXF1ZXN0IkwKFkxpc3RDb250YWluZXJzUmVzcG9uc2USMgoKY29udGFpbmVycxgBIAMoCzIeLmRvY2tlcl9ycGMudjEuRG9ja2VyQ29udGFpbmVyIhIKEE5ld0ltYWdlUmVzcG9uc2UiIwoPTmV3SW1hZ2VSZXF1ZXN0EhAKCGltYWdlVGFnGAEgASgJIj8KEUxpc3RJbWFnZVJlc3BvbnNlEioKBmltYWdlcxgBIAMoCzIaLmRvY2tlcl9ycGMudjEuRG9ja2VySW1hZ2UiEgoQTGlzdEltYWdlUmVxdWVzdCJRCg9Eb2NrZXJDb250YWluZXISCgoCaWQYASABKAkSMgoIbWV0YWRhdGEYAiADKAsyIC5kb2NrZXJfcnBjLnYxLkNvbnRhaW5lck1ldGFEYXRhImUKEUNvbnRhaW5lck1ldGFEYXRhEgoKAklkGAEgASgJEhYKDkNvbnRhaW5lck5hbWVzGAIgAygJEg0KBUltYWdlGAQgASgJEg4KBlN0YXR1cxgDIAEoCRINCgVTdGF0ZRgGIAEoCSJJCgtEb2NrZXJJbWFnZRIKCgJpZBgBIAEoCRIuCghtZXRhZGF0YRgCIAMoCzIcLmRvY2tlcl9ycGMudjEuSW1hZ2VNZXRhRGF0YSJOCg1JbWFnZU1ldGFEYXRhEgoKAklkGAEgASgJEhAKCFJlcG9UYWdzGAIgAygJEgwKBFNpemUYAyABKAMSEQoJQ3JlYXRlZEF0GAQgASgDMoYGCg1Eb2NrZXJTZXJ2aWNlEmIKD0NyZWF0ZUNvbnRhaW5lchIlLmRvY2tlcl9ycGMudjEuQ3JlYXRlQ29udGFpbmVyUmVxdWVzdBomLmRvY2tlcl9ycGMudjEuQ3JlYXRlQ29udGFpbmVyUmVzcG9uc2UiABJiCg9EZWxldGVDb250YWluZXISJS5kb2NrZXJfcnBjLnYxLkRlbGV0ZUNvbnRhaW5lclJlcXVlc3QaJi5kb2NrZXJfcnBjLnYxLkRlbGV0ZUNvbnRhaW5lclJlc3BvbnNlIgASXwoOTGlzdENvbnRhaW5lcnMSJC5kb2NrZXJfcnBjLnYxLkxpc3RDb250YWluZXJzUmVxdWVzdBolLmRvY2tlcl9ycGMudjEuTGlzdENvbnRhaW5lcnNSZXNwb25zZSIAEl8KDlN0YXJ0Q29udGFpbmVyEiQuZG9ja2VyX3JwYy52MS5TdGFydENvbnRhaW5lclJlcXVlc3QaJS5kb2NrZXJfcnBjLnYxLlN0YXJ0Q29udGFpbmVyUmVzcG9uc2UiABJcCg1TdG9wQ29udGFpbmVyEiMuZG9ja2VyX3JwYy52MS5TdG9wQ29udGFpbmVyUmVxdWVzdBokLmRvY2tlcl9ycGMudjEuU3RvcENvbnRhaW5lclJlc3BvbnNlIgASZQoQR2V0Q29udGFpbmVyTG9ncxIlLmRvY2tlcl9ycGMudjEuR2V0Q29udGFpbmVyTG9nUmVxdWVzdBomLmRvY2tlcl9ycGMudjEuR2V0Q29udGFpbmVyTG9nUmVzcG9uc2UiADABElMKDkNyZWF0ZU5ld0ltYWdlEh4uZG9ja2VyX3JwYy52MS5OZXdJbWFnZVJlcXVlc3QaHy5kb2NrZXJfcnBjLnYxLk5ld0ltYWdlUmVzcG9uc2UiABJRCgpMaXN0SW1hZ2VzEh8uZG9ja2VyX3JwYy52MS5MaXN0SW1hZ2VSZXF1ZXN0GiAuZG9ja2VyX3JwYy52MS5MaXN0SW1hZ2VSZXNwb25zZSIAQq4BChFjb20uZG9ja2VyX3JwYy52MUILRG9ja2VyUHJvdG9QAVo7Z2l0aHViLmNvbS9tYWtlb3BlbnNvdXJjZS9sZXZpYXRoYW4vZ2VuZXJhdGVkL2RvY2tlcl9ycGMvdjGiAgNEWFiqAgxEb2NrZXJScGMuVjHKAgxEb2NrZXJScGNcVjHiAhhEb2NrZXJScGNcVjFcR1BCTWV0YWRhdGHqAg1Eb2NrZXJScGM6OlYxYgZwcm90bzM", [file_types_v1_types]); /** * @generated from message docker_rpc.v1.StartContainerRequest @@ -228,11 +227,6 @@ export type NewImageRequest = Message<"docker_rpc.v1.NewImageRequest"> & { * @generated from field: string imageTag = 1; */ imageTag: string; - - /** - * @generated from field: types.v1.FileUpload file = 2; - */ - file?: FileUpload; }; /** diff --git a/spec/leviathan_node/src/generated/jobs/v1/jobs_pb.ts b/spec/leviathan_node/src/generated/jobs/v1/jobs_pb.ts index e8455d4..18c0c14 100644 --- a/spec/leviathan_node/src/generated/jobs/v1/jobs_pb.ts +++ b/spec/leviathan_node/src/generated/jobs/v1/jobs_pb.ts @@ -4,7 +4,6 @@ import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; -import type { FileUpload, MachineLimits } from "../../types/v1/types_pb"; import { file_types_v1_types } from "../../types/v1/types_pb"; import type { Message } from "@bufbuild/protobuf"; @@ -12,41 +11,21 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file jobs/v1/jobs.proto. */ export const file_jobs_v1_jobs: GenFile = /*@__PURE__*/ - fileDesc("ChJqb2JzL3YxL2pvYnMucHJvdG8SB2pvYnMudjEizAEKDU5ld0pvYlJlcXVlc3QSJgoIam9iRmlsZXMYASADKAsyFC50eXBlcy52MS5GaWxlVXBsb2FkEigKCmRvY2tlckZpbGUYAiABKAsyFC50eXBlcy52MS5GaWxlVXBsb2FkEhEKCWltYWdlTmFtZRgDIAEoCRIbChNqb2JUaW1lb3V0SW5TZWNvbmRzGAQgASgEEhAKCGVudHJ5Q21kGAUgASgJEicKBmxpbWl0cxgGIAEoCzIXLnR5cGVzLnYxLk1hY2hpbmVMaW1pdHMiHwoOTmV3Sm9iUmVzcG9uc2USDQoFam9iSWQYASABKAkiIQoQQ2FuY2VsSm9iUmVxdWVzdBINCgVqb2JJZBgBIAEoCSITChFDYW5jZWxKb2JSZXNwb25zZSIeCg1Kb2JMb2dSZXF1ZXN0Eg0KBWpvYklkGAEgASgJIkQKD0pvYkxvZ3NSZXNwb25zZRIjCgdqb2JJbmZvGAEgASgLMhIuam9icy52MS5Kb2JTdGF0dXMSDAoEbG9ncxgCIAEoCSJDCglKb2JTdGF0dXMSDgoGam9iX2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIWCg5zdGF0dXNfbWVzc2FnZRgDIAEoCTKWAgoKSm9iU2VydmljZRI7CgZOZXdKb2ISFi5qb2JzLnYxLk5ld0pvYlJlcXVlc3QaFy5qb2JzLnYxLk5ld0pvYlJlc3BvbnNlIgASPwoJR2V0U3RhdHVzEhYuam9icy52MS5Kb2JMb2dSZXF1ZXN0Ghguam9icy52MS5Kb2JMb2dzUmVzcG9uc2UiABJECgxTdHJlYW1TdGF0dXMSFi5qb2JzLnYxLkpvYkxvZ1JlcXVlc3QaGC5qb2JzLnYxLkpvYkxvZ3NSZXNwb25zZSIAMAESRAoJQ2FuY2VsSm9iEhkuam9icy52MS5DYW5jZWxKb2JSZXF1ZXN0Ghouam9icy52MS5DYW5jZWxKb2JSZXNwb25zZSIAQowBCgtjb20uam9icy52MUIJSm9ic1Byb3RvUAFaNWdpdGh1Yi5jb20vbWFrZW9wZW5zb3VyY2UvbGV2aWF0aGFuL2dlbmVyYXRlZC9qb2JzL3YxogIDSlhYqgIHSm9icy5WMcoCB0pvYnNcVjHiAhNKb2JzXFYxXEdQQk1ldGFkYXRh6gIISm9iczo6VjFiBnByb3RvMw", [file_types_v1_types]); + fileDesc("ChJqb2JzL3YxL2pvYnMucHJvdG8SB2pvYnMudjEiPQoNTmV3Sm9iUmVxdWVzdBINCgVsYWJJRBgBIAEoBBIdChV0bXBTdWJtaXNzaW9uRm9sZGVySWQYAiABKAkiHwoOTmV3Sm9iUmVzcG9uc2USDQoFam9iSWQYASABKAkiIQoQQ2FuY2VsSm9iUmVxdWVzdBINCgVqb2JJZBgBIAEoCSITChFDYW5jZWxKb2JSZXNwb25zZSIeCg1Kb2JMb2dSZXF1ZXN0Eg0KBWpvYklkGAEgASgJIkQKD0pvYkxvZ3NSZXNwb25zZRIjCgdqb2JJbmZvGAEgASgLMhIuam9icy52MS5Kb2JTdGF0dXMSDAoEbG9ncxgCIAEoCSJDCglKb2JTdGF0dXMSDgoGam9iX2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIWCg5zdGF0dXNfbWVzc2FnZRgDIAEoCTKWAgoKSm9iU2VydmljZRI7CgZOZXdKb2ISFi5qb2JzLnYxLk5ld0pvYlJlcXVlc3QaFy5qb2JzLnYxLk5ld0pvYlJlc3BvbnNlIgASPwoJR2V0U3RhdHVzEhYuam9icy52MS5Kb2JMb2dSZXF1ZXN0Ghguam9icy52MS5Kb2JMb2dzUmVzcG9uc2UiABJECgxTdHJlYW1TdGF0dXMSFi5qb2JzLnYxLkpvYkxvZ1JlcXVlc3QaGC5qb2JzLnYxLkpvYkxvZ3NSZXNwb25zZSIAMAESRAoJQ2FuY2VsSm9iEhkuam9icy52MS5DYW5jZWxKb2JSZXF1ZXN0Ghouam9icy52MS5DYW5jZWxKb2JSZXNwb25zZSIAQowBCgtjb20uam9icy52MUIJSm9ic1Byb3RvUAFaNWdpdGh1Yi5jb20vbWFrZW9wZW5zb3VyY2UvbGV2aWF0aGFuL2dlbmVyYXRlZC9qb2JzL3YxogIDSlhYqgIHSm9icy5WMcoCB0pvYnNcVjHiAhNKb2JzXFYxXEdQQk1ldGFkYXRh6gIISm9iczo6VjFiBnByb3RvMw", [file_types_v1_types]); /** * @generated from message jobs.v1.NewJobRequest */ export type NewJobRequest = Message<"jobs.v1.NewJobRequest"> & { /** - * @generated from field: repeated types.v1.FileUpload jobFiles = 1; + * @generated from field: uint64 labID = 1; */ - jobFiles: FileUpload[]; + labID: bigint; /** - * @generated from field: types.v1.FileUpload dockerFile = 2; + * @generated from field: string tmpSubmissionFolderId = 2; */ - dockerFile?: FileUpload; - - /** - * @generated from field: string imageName = 3; - */ - imageName: string; - - /** - * @generated from field: uint64 jobTimeoutInSeconds = 4; - */ - jobTimeoutInSeconds: bigint; - - /** - * @generated from field: string entryCmd = 5; - */ - entryCmd: string; - - /** - * @generated from field: types.v1.MachineLimits limits = 6; - */ - limits?: MachineLimits; + tmpSubmissionFolderId: string; }; /** diff --git a/spec/leviathan_node/src/generated/labs/v1/labs_pb.ts b/spec/leviathan_node/src/generated/labs/v1/labs_pb.ts index 0cae790..b8ae3c2 100644 --- a/spec/leviathan_node/src/generated/labs/v1/labs_pb.ts +++ b/spec/leviathan_node/src/generated/labs/v1/labs_pb.ts @@ -4,7 +4,7 @@ import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; -import type { FileUpload, MachineLimits } from "../../types/v1/types_pb"; +import type { LabData, LabDataSchema } from "../../types/v1/types_pb"; import { file_types_v1_types } from "../../types/v1/types_pb"; import type { Message } from "@bufbuild/protobuf"; @@ -12,48 +12,28 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file labs/v1/labs.proto. */ export const file_labs_v1_labs: GenFile = /*@__PURE__*/ - fileDesc("ChJsYWJzL3YxL2xhYnMucHJvdG8SB2xhYnMudjEi0QEKCkxhYlJlcXVlc3QSDwoHbGFiTmFtZRgBIAEoCRIUCgxlbnRyeUNvbW1hbmQYAiABKAkSGgoSdGltZUxpbWl0SW5TZWNvbmRzGAQgASgEEi4KDW1hY2hpbmVMaW1pdHMYBSABKAsyFy50eXBlcy52MS5NYWNoaW5lTGltaXRzEigKCmRvY2tlckZpbGUYBiABKAsyFC50eXBlcy52MS5GaWxlVXBsb2FkEiYKCGpvYkZpbGVzGAcgAygLMhQudHlwZXMudjEuRmlsZVVwbG9hZCIfCg5OZXdMYWJSZXNwb25zZRINCgVsYWJJZBgBIAEoAyJFCg5FZGl0TGFiUmVxdWVzdBINCgVsYWJJZBgBIAEoAxIkCgdsYWJJbmZvGAIgASgLMhMubGFicy52MS5MYWJSZXF1ZXN0IhEKD0VkaXRMYWJSZXNwb25zZSIhChBEZWxldGVMYWJSZXF1ZXN0Eg0KBUxhYklEGAEgASgDIhMKEURlbGV0ZUxhYlJlc3BvbnNlMsgBCgpMYWJTZXJ2aWNlEjgKBk5ld0xhYhITLmxhYnMudjEuTGFiUmVxdWVzdBoXLmxhYnMudjEuTmV3TGFiUmVzcG9uc2UiABI6CgdFZGl0TGFiEhMubGFicy52MS5MYWJSZXF1ZXN0GhgubGFicy52MS5FZGl0TGFiUmVzcG9uc2UiABJECglEZWxldGVMYWISGS5sYWJzLnYxLkRlbGV0ZUxhYlJlcXVlc3QaGi5sYWJzLnYxLkRlbGV0ZUxhYlJlc3BvbnNlIgBCjAEKC2NvbS5sYWJzLnYxQglMYWJzUHJvdG9QAVo1Z2l0aHViLmNvbS9tYWtlb3BlbnNvdXJjZS9sZXZpYXRoYW4vZ2VuZXJhdGVkL2xhYnMvdjGiAgNMWFiqAgdMYWJzLlYxygIHTGFic1xWMeICE0xhYnNcVjFcR1BCTWV0YWRhdGHqAghMYWJzOjpWMWIGcHJvdG8z", [file_types_v1_types]); + fileDesc("ChJsYWJzL3YxL2xhYnMucHJvdG8SB2xhYnMudjEiSAoNTmV3TGFiUmVxdWVzdBITCgt0bXBGb2xkZXJJZBgBIAEoCRIiCgdsYWJEYXRhGAIgASgLMhEudHlwZXMudjEuTGFiRGF0YSIfCg5OZXdMYWJSZXNwb25zZRINCgVsYWJJZBgBIAEoAyJDCg5FZGl0TGFiUmVxdWVzdBINCgVsYWJJZBgBIAEoAxIiCgdsYWJJbmZvGAIgASgLMhEudHlwZXMudjEuTGFiRGF0YSIRCg9FZGl0TGFiUmVzcG9uc2UiIQoQRGVsZXRlTGFiUmVxdWVzdBINCgVMYWJJRBgBIAEoAyITChFEZWxldGVMYWJSZXNwb25zZTLJAQoKTGFiU2VydmljZRI7CgZOZXdMYWISFi5sYWJzLnYxLk5ld0xhYlJlcXVlc3QaFy5sYWJzLnYxLk5ld0xhYlJlc3BvbnNlIgASOAoHRWRpdExhYhIRLnR5cGVzLnYxLkxhYkRhdGEaGC5sYWJzLnYxLkVkaXRMYWJSZXNwb25zZSIAEkQKCURlbGV0ZUxhYhIZLmxhYnMudjEuRGVsZXRlTGFiUmVxdWVzdBoaLmxhYnMudjEuRGVsZXRlTGFiUmVzcG9uc2UiAEKMAQoLY29tLmxhYnMudjFCCUxhYnNQcm90b1ABWjVnaXRodWIuY29tL21ha2VvcGVuc291cmNlL2xldmlhdGhhbi9nZW5lcmF0ZWQvbGFicy92MaICA0xYWKoCB0xhYnMuVjHKAgdMYWJzXFYx4gITTGFic1xWMVxHUEJNZXRhZGF0YeoCCExhYnM6OlYxYgZwcm90bzM", [file_types_v1_types]); /** - * @generated from message labs.v1.LabRequest + * @generated from message labs.v1.NewLabRequest */ -export type LabRequest = Message<"labs.v1.LabRequest"> & { +export type NewLabRequest = Message<"labs.v1.NewLabRequest"> & { /** - * @generated from field: string labName = 1; + * @generated from field: string tmpFolderId = 1; */ - labName: string; + tmpFolderId: string; /** - * @generated from field: string entryCommand = 2; + * @generated from field: types.v1.LabData labData = 2; */ - entryCommand: string; - - /** - * @generated from field: uint64 timeLimitInSeconds = 4; - */ - timeLimitInSeconds: bigint; - - /** - * @generated from field: types.v1.MachineLimits machineLimits = 5; - */ - machineLimits?: MachineLimits; - - /** - * @generated from field: types.v1.FileUpload dockerFile = 6; - */ - dockerFile?: FileUpload; - - /** - * @generated from field: repeated types.v1.FileUpload jobFiles = 7; - */ - jobFiles: FileUpload[]; + labData?: LabData; }; /** - * Describes the message labs.v1.LabRequest. - * Use `create(LabRequestSchema)` to create a new message. + * Describes the message labs.v1.NewLabRequest. + * Use `create(NewLabRequestSchema)` to create a new message. */ -export const LabRequestSchema: GenMessage = /*@__PURE__*/ +export const NewLabRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_labs_v1_labs, 0); /** @@ -83,9 +63,9 @@ export type EditLabRequest = Message<"labs.v1.EditLabRequest"> & { labId: bigint; /** - * @generated from field: labs.v1.LabRequest labInfo = 2; + * @generated from field: types.v1.LabData labInfo = 2; */ - labInfo?: LabRequest; + labInfo?: LabData; }; /** @@ -147,7 +127,7 @@ export const LabService: GenService<{ */ newLab: { methodKind: "unary"; - input: typeof LabRequestSchema; + input: typeof NewLabRequestSchema; output: typeof NewLabResponseSchema; }, /** @@ -155,7 +135,7 @@ export const LabService: GenService<{ */ editLab: { methodKind: "unary"; - input: typeof LabRequestSchema; + input: typeof LabDataSchema; output: typeof EditLabResponseSchema; }, /** diff --git a/spec/leviathan_node/src/generated/types/v1/types_pb.ts b/spec/leviathan_node/src/generated/types/v1/types_pb.ts index 3335b64..bb46884 100644 --- a/spec/leviathan_node/src/generated/types/v1/types_pb.ts +++ b/spec/leviathan_node/src/generated/types/v1/types_pb.ts @@ -10,28 +10,43 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file types/v1/types.proto. */ export const file_types_v1_types: GenFile = /*@__PURE__*/ - fileDesc("ChR0eXBlcy92MS90eXBlcy5wcm90bxIIdHlwZXMudjEiLwoKRmlsZVVwbG9hZBIQCghmaWxlbmFtZRgBIAEoCRIPCgdjb250ZW50GAIgASgMIkcKDU1hY2hpbmVMaW1pdHMSEAoIQ1BVQ29yZXMYASABKAUSEgoKbWVtb3J5SW5NYhgCIAEoBRIQCghQaWRMaW1pdBgDIAEoBUKTAQoMY29tLnR5cGVzLnYxQgpUeXBlc1Byb3RvUAFaNmdpdGh1Yi5jb20vbWFrZW9wZW5zb3VyY2UvbGV2aWF0aGFuL2dlbmVyYXRlZC90eXBlcy92MaICA1RYWKoCCFR5cGVzLlYxygIIVHlwZXNcVjHiAhRUeXBlc1xWMVxHUEJNZXRhZGF0YeoCCVR5cGVzOjpWMWIGcHJvdG8z"); + fileDesc("ChR0eXBlcy92MS90eXBlcy5wcm90bxIIdHlwZXMudjEilAEKB0xhYkRhdGESDwoHbGFibmFtZRgBIAEoCRIQCghlbnRyeUNtZBgCIAEoCRIbChNqb2JUaW1lb3V0SW5TZWNvbmRzGAMgASgEEiAKGGF1dG9sYWJDb21wYXRpYmlsaXR5TW9kZRgEIAEoCBInCgZsaW1pdHMYBSABKAsyFy50eXBlcy52MS5NYWNoaW5lTGltaXRzIkcKDU1hY2hpbmVMaW1pdHMSEAoIQ1BVQ29yZXMYASABKAUSEgoKbWVtb3J5SW5NYhgCIAEoBRIQCghQaWRMaW1pdBgDIAEoBUKTAQoMY29tLnR5cGVzLnYxQgpUeXBlc1Byb3RvUAFaNmdpdGh1Yi5jb20vbWFrZW9wZW5zb3VyY2UvbGV2aWF0aGFuL2dlbmVyYXRlZC90eXBlcy92MaICA1RYWKoCCFR5cGVzLlYxygIIVHlwZXNcVjHiAhRUeXBlc1xWMVxHUEJNZXRhZGF0YeoCCVR5cGVzOjpWMWIGcHJvdG8z"); /** - * @generated from message types.v1.FileUpload + * @generated from message types.v1.LabData */ -export type FileUpload = Message<"types.v1.FileUpload"> & { +export type LabData = Message<"types.v1.LabData"> & { /** - * @generated from field: string filename = 1; + * @generated from field: string labname = 1; */ - filename: string; + labname: string; /** - * @generated from field: bytes content = 2; + * @generated from field: string entryCmd = 2; */ - content: Uint8Array; + entryCmd: string; + + /** + * @generated from field: uint64 jobTimeoutInSeconds = 3; + */ + jobTimeoutInSeconds: bigint; + + /** + * @generated from field: bool autolabCompatibilityMode = 4; + */ + autolabCompatibilityMode: boolean; + + /** + * @generated from field: types.v1.MachineLimits limits = 5; + */ + limits?: MachineLimits; }; /** - * Describes the message types.v1.FileUpload. - * Use `create(FileUploadSchema)` to create a new message. + * Describes the message types.v1.LabData. + * Use `create(LabDataSchema)` to create a new message. */ -export const FileUploadSchema: GenMessage = /*@__PURE__*/ +export const LabDataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_types_v1_types, 0); /** diff --git a/spec/leviathan_node/src/index.ts b/spec/leviathan_node/src/index.ts index aac7935..f0400ed 100644 --- a/spec/leviathan_node/src/index.ts +++ b/spec/leviathan_node/src/index.ts @@ -5,3 +5,56 @@ export * from "./generated/stats/v1/stats_pb" export * from "./generated/types/v1/types_pb" export * from "@connectrpc/connect-node" export * from "@connectrpc/connect" + +// Base type with common properties +type BaseFileData = { + filename: string, + filedata: Blob +} + +// Create specific types with preset field names +export type SubmissionFile = BaseFileData & { fieldName: 'submissionFiles' } +export type DockerFile = BaseFileData & { fieldName: 'dockerfile' } +export type LabFile = BaseFileData & { fieldName: 'labFiles' } + +// Union type of all possible file data types +type FileData = SubmissionFile | DockerFile | LabFile + +export async function UploadLabFiles(basePath: String, dockerfile: DockerFile, files: Array) { + const url = `${basePath}/files.v1/upload/lab` + return UploadMultipartForm(url, [...files, dockerfile]) +} + +export async function UploadSubmissionFiles(basePath: String, files: Array) { + const url = `${basePath}/files.v1/upload/submission` + return UploadMultipartForm(url, files) +} + +async function UploadMultipartForm(url: string, files: Array,) { + const formData = new FormData(); + + for (const info of files) { + formData.append( + info.fieldName, + info.filedata, + info.filename, + ); + } + + const response = await fetch(url, { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error(`Upload failed with status: ${response.status}\n${response.statusText}\n${response.body}`); + } + + const data = await response.json(); + if (data && data.folderId) { + return data.folderId as string; + } else { + throw new Error('Response did not contain an folderID'); + } +} + diff --git a/spec/leviathan_node/src/index_test.ts b/spec/leviathan_node/src/index_test.ts new file mode 100644 index 0000000..16a5b57 --- /dev/null +++ b/spec/leviathan_node/src/index_test.ts @@ -0,0 +1,57 @@ +import {DockerFile, UploadLabFiles, UploadSubmissionFiles} from "./index"; + +async function test_lab_upload() { + const basePath = "http://localhost:9221" + + const res = await UploadLabFiles(basePath, { + fieldName: 'dockerfile', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}) + }, [ + { + fieldName: 'labFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}), + + }, { + fieldName: 'labFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}), + + }, { + fieldName: 'labFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}), + + }, { + fieldName: 'labFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}), + + }, { + fieldName: 'labFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}), + }, + ]) + + + console.log(res) +} + +const basePath = "http://localhost:9221" + +async function test_submission_upload() { + + const res = await UploadSubmissionFiles(basePath, [{ + fieldName: 'submissionFiles', + filename: "test.txt", + filedata: new Blob([Buffer.from("test")], {type: "text/plain"}) + }]) + + console.log(res) +} + +test_submission_upload() +test_lab_upload() +// test() \ No newline at end of file diff --git a/spec/proto/docker_rpc/v1/docker.proto b/spec/proto/docker_rpc/v1/docker.proto index f04a079..b70ccae 100644 --- a/spec/proto/docker_rpc/v1/docker.proto +++ b/spec/proto/docker_rpc/v1/docker.proto @@ -61,7 +61,6 @@ message NewImageResponse {} message NewImageRequest { string imageTag = 1; - types.v1.FileUpload file = 2; } message ListImageResponse{ diff --git a/spec/proto/jobs/v1/jobs.proto b/spec/proto/jobs/v1/jobs.proto index 6327021..4d7cdb9 100644 --- a/spec/proto/jobs/v1/jobs.proto +++ b/spec/proto/jobs/v1/jobs.proto @@ -16,12 +16,8 @@ service JobService { } message NewJobRequest { - repeated types.v1.FileUpload jobFiles = 1; - types.v1.FileUpload dockerFile = 2; - string imageName = 3; - uint64 jobTimeoutInSeconds = 4; - string entryCmd = 5; - types.v1.MachineLimits limits = 6; + uint64 labID = 1; + string tmpSubmissionFolderId = 2; } message NewJobResponse { diff --git a/spec/proto/labs/v1/labs.proto b/spec/proto/labs/v1/labs.proto index a531d64..fa0be17 100644 --- a/spec/proto/labs/v1/labs.proto +++ b/spec/proto/labs/v1/labs.proto @@ -7,18 +7,14 @@ option go_package = "github.com/makeopensource/leviathan/generated/labs/v1"; import "types/v1/types.proto"; service LabService { - rpc NewLab(LabRequest) returns (NewLabResponse) {} - rpc EditLab(LabRequest) returns (EditLabResponse) {} + rpc NewLab(NewLabRequest) returns (NewLabResponse) {} + rpc EditLab(types.v1.LabData) returns (EditLabResponse) {} rpc DeleteLab(DeleteLabRequest) returns (DeleteLabResponse) {} } -message LabRequest { - string labName = 1; - string entryCommand = 2; - uint64 timeLimitInSeconds = 4; - types.v1.MachineLimits machineLimits = 5; - types.v1.FileUpload dockerFile = 6; - repeated types.v1.FileUpload jobFiles = 7; +message NewLabRequest { + string tmpFolderId = 1; + types.v1.LabData labData = 2; } message NewLabResponse { @@ -27,7 +23,7 @@ message NewLabResponse { message EditLabRequest { int64 labId = 1; - LabRequest labInfo = 2; + types.v1.LabData labInfo = 2; } message EditLabResponse {} diff --git a/spec/proto/types/v1/types.proto b/spec/proto/types/v1/types.proto index 87ee057..b8da5ff 100644 --- a/spec/proto/types/v1/types.proto +++ b/spec/proto/types/v1/types.proto @@ -4,9 +4,12 @@ package types.v1; option go_package = "github.com/makeopensource/leviathan/generated/types/v1"; -message FileUpload { - string filename = 1; - bytes content = 2; +message LabData { + string labname = 1; + string entryCmd = 2; + uint64 jobTimeoutInSeconds = 3; + bool autolabCompatibilityMode = 4; + MachineLimits limits = 5; } message MachineLimits { diff --git a/src/api/api.go b/src/api/api.go index 2f5f79a..7b3b63b 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -42,7 +42,7 @@ func setupEndpoints() *http.ServeMux { interceptor = connect.WithInterceptors(&authInterceptor{common.ApiKey.GetStr()}) } - endpoints := []func() (string, http.Handler){ + v1Endpoints := []func() (string, http.Handler){ // jobs endpoints func() (string, http.Handler) { jobSrv := v1.NewJobServer(job) @@ -57,10 +57,14 @@ func setupEndpoints() *http.ServeMux { labSrv := v1.LabServer{Srv: lab} return labClient.NewLabServiceHandler(labSrv, interceptor) }, + func() (string, http.Handler) { + fileHandler := v1.NewFileManagerHandler("/files.v1") + return fileHandler.BasePath + "/", fileHandler + }, } mux := http.NewServeMux() - for _, svc := range endpoints { + for _, svc := range v1Endpoints { path, handler := svc() mux.Handle(path, handler) } diff --git a/src/api/v1/file_manager_impl.go b/src/api/v1/file_manager_impl.go new file mode 100644 index 0000000..d1c0f15 --- /dev/null +++ b/src/api/v1/file_manager_impl.go @@ -0,0 +1,169 @@ +package v1 + +import ( + "encoding/json" + . "github.com/makeopensource/leviathan/common" + . "github.com/makeopensource/leviathan/service/file_manager" + "github.com/rs/zerolog/log" + "mime/multipart" + "net/http" +) + +const ( + maxMemory = 32 << 20 // 32 MB for multipart forms + SubmissionFilesKey = "submissionFiles" + DockerFileKey = "dockerfile" + LabFilesKey = "labFiles" +) + +type FileManagerHandler struct { + BasePath string + UploadLabPath string + UploadSubmissionPath string + service FileManagerService +} + +func NewFileManagerHandler(basePath string) *FileManagerHandler { + return &FileManagerHandler{ + BasePath: basePath, + UploadLabPath: basePath + "/upload/lab", + UploadSubmissionPath: basePath + "/upload/submission", + service: FileManagerService{}, + } +} + +func (f *FileManagerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + if r.URL.String() == f.UploadLabPath { + f.UploadLabData(w, r) + return + } else if r.URL.String() == f.UploadSubmissionPath { + f.UploadSubmissionData(w, r) + return + } + } + + w.WriteHeader(http.StatusMethodNotAllowed) + return +} + +func (f *FileManagerHandler) UploadLabData(w http.ResponseWriter, r *http.Request) { + // Parse the multipart form with reasonable memory limit + if err := r.ParseMultipartForm(maxMemory); err != nil { + http.Error(w, "Failed to parse form: "+err.Error(), http.StatusBadRequest) + return + } + + dockerFile, _, err := r.FormFile(DockerFileKey) + if err != nil { + http.Error( + w, + ErrLog("Failed to get dockerfile in form", err, log.Error()).Error(), + http.StatusBadRequest, + ) + return + } + defer CloseFile(dockerFile) + + jobFiles, ok := r.MultipartForm.File[LabFilesKey] + if !ok || len(jobFiles) == 0 { + http.Error(w, "No jobFiles in form", http.StatusBadRequest) + return + } + + fileInfos, err := mapToFileInfo(jobFiles) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer func(files []*FileInfo) { + for _, file := range files { + CloseFile(file.Reader) + } + }(fileInfos) + + folderID, err := f.service.CreateTmpLabFolder(dockerFile, fileInfos...) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + sendResponse(w, err, folderID) +} + +func (f *FileManagerHandler) UploadSubmissionData(w http.ResponseWriter, r *http.Request) { + // Parse the multipart form with reasonable memory limit + if err := r.ParseMultipartForm(maxMemory); err != nil { + http.Error(w, "Failed to parse form: "+err.Error(), http.StatusBadRequest) + return + } + + jobFiles, ok := r.MultipartForm.File[SubmissionFilesKey] + if !ok || len(jobFiles) == 0 { + http.Error(w, "No submission files found in form", http.StatusBadRequest) + return + } + + fileInfos, err := mapToFileInfo(jobFiles) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer func(files []*FileInfo) { + for _, file := range files { + CloseFile(file.Reader) + } + }(fileInfos) + + folderID, err := f.service.CreateSubmissionFolder(fileInfos...) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + sendResponse(w, err, folderID) +} + +func sendResponse(w http.ResponseWriter, err error, folderID string) { + jsonData, err := toJson(folderID, err) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, err = w.Write(jsonData) + if err != nil { + http.Error( + w, + ErrLog("Failed to write response", err, log.Error()).Error(), + http.StatusInternalServerError, + ) + return + } +} + +func toJson(folderID string, err error) ([]byte, error) { + resultMap := map[string]string{ + "folderId": folderID, + } + jsonData, err := json.Marshal(resultMap) + if err != nil { + return nil, ErrLog("Failed to marshal json", err, log.Error()) + } + return jsonData, nil +} + +func mapToFileInfo(jobFiles []*multipart.FileHeader) ([]*FileInfo, error) { + var fileInfos []*FileInfo + for _, jobFile := range jobFiles { + file, err := jobFile.Open() + if err != nil { + return fileInfos, ErrLog("unable to open file: "+err.Error(), err, log.Error()) + } + fileInfos = append(fileInfos, &FileInfo{ + Reader: file, + Filename: jobFile.Filename, + }) + } + return fileInfos, nil +} diff --git a/src/api/v1/job_impl.go b/src/api/v1/job_impl.go index 5fd1669..ca44db4 100644 --- a/src/api/v1/job_impl.go +++ b/src/api/v1/job_impl.go @@ -7,8 +7,6 @@ import ( v1 "github.com/makeopensource/leviathan/generated/jobs/v1" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/jobs" - "strings" - "time" ) type JobServer struct { @@ -19,42 +17,15 @@ func NewJobServer(srv *jobs.JobService) *JobServer { return &JobServer{srv: srv} } -func (job *JobServer) NewJob(ctx context.Context, req *connect.Request[v1.NewJobRequest]) (*connect.Response[v1.NewJobResponse], error) { - jobFiles := req.Msg.GetJobFiles() - tag := req.Msg.GetImageName() - dockerfile := req.Msg.GetDockerFile() - entryCmd := req.Msg.GetEntryCmd() - - for i, v := range jobFiles { - if v == nil { - return nil, fmt.Errorf("nil file at index %d", i) - } else if v.Filename == "" { - return nil, fmt.Errorf("filename is empty") - } else if len(v.Content) == 0 { - return nil, fmt.Errorf("empty content for file %s", v.Filename) - } - } - - if tag == "" { - return nil, fmt.Errorf("docker image tag is empty") - } - if entryCmd == "" { - return nil, fmt.Errorf("entry cmd is empty") - } - - lab := models.Lab{ - ImageTag: strings.ToLower(strings.TrimSpace(tag)), - JobEntryCmd: entryCmd, - JobTimeout: time.Second * time.Duration(req.Msg.JobTimeoutInSeconds), - JobLimits: models.MachineLimits{ - PidsLimit: int64(req.Msg.GetLimits().PidLimit), - NanoCPU: int64(req.Msg.GetLimits().CPUCores), - Memory: int64(req.Msg.GetLimits().MemoryInMb), - }, +func (job *JobServer) NewJob(_ context.Context, req *connect.Request[v1.NewJobRequest]) (*connect.Response[v1.NewJobResponse], error) { + labId := req.Msg.LabID + submissionTmpFolder := req.Msg.TmpSubmissionFolderId + if submissionTmpFolder == "" { + return nil, fmt.Errorf("submission folder id is empty") } - newJob := &models.Job{LabData: &lab} - jobId, err := job.srv.NewJobFromRPC(newJob, jobFiles, dockerfile) + newJob := &models.Job{LabID: uint(labId)} + jobId, err := job.srv.NewJob(newJob, submissionTmpFolder) if err != nil { return nil, err } @@ -91,7 +62,7 @@ func (job *JobServer) StreamStatus(ctx context.Context, req *connect.Request[v1. return nil } -func (job *JobServer) CancelJob(ctx context.Context, req *connect.Request[v1.CancelJobRequest]) (*connect.Response[v1.CancelJobResponse], error) { +func (job *JobServer) CancelJob(_ context.Context, req *connect.Request[v1.CancelJobRequest]) (*connect.Response[v1.CancelJobResponse], error) { msgId := req.Msg.GetJobId() if msgId == "" { return nil, fmt.Errorf("job Id is empty") diff --git a/src/api/v1/lab_impl.go b/src/api/v1/lab_impl.go index 130bcd2..d6f262f 100644 --- a/src/api/v1/lab_impl.go +++ b/src/api/v1/lab_impl.go @@ -5,6 +5,7 @@ import ( "context" "fmt" v1 "github.com/makeopensource/leviathan/generated/labs/v1" + typesv1 "github.com/makeopensource/leviathan/generated/types/v1" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/labs" "time" @@ -14,27 +15,28 @@ type LabServer struct { Srv *labs.LabService } -func (l LabServer) NewLab(ctx context.Context, req *connect.Request[v1.LabRequest]) (*connect.Response[v1.NewLabResponse], error) { - if req.Msg.LabName == "" { +func (l LabServer) NewLab(ctx context.Context, req *connect.Request[v1.NewLabRequest]) (*connect.Response[v1.NewLabResponse], error) { + if req.Msg.LabData.Labname == "" { return nil, fmt.Errorf("lab name is required") - } else if req.Msg.EntryCommand == "" { - return nil, fmt.Errorf("entry command is required") - } else if req.Msg.TimeLimitInSeconds == 0 { + } else if req.Msg.LabData.JobTimeoutInSeconds == 0 { return nil, fmt.Errorf("0 seconds as time limit is not allowed") + } else if req.Msg.TmpFolderId == "" { + return nil, fmt.Errorf("tmp folder id is required") } lab := &models.Lab{ - Name: req.Msg.LabName, - JobTimeout: time.Duration(req.Msg.TimeLimitInSeconds) * time.Second, + Name: req.Msg.LabData.Labname, + JobTimeout: time.Duration(req.Msg.LabData.JobTimeoutInSeconds) * time.Second, JobLimits: models.MachineLimits{ - PidsLimit: int64(req.Msg.MachineLimits.PidLimit), - NanoCPU: int64(req.Msg.MachineLimits.CPUCores), - Memory: int64(req.Msg.MachineLimits.MemoryInMb), + PidsLimit: int64(req.Msg.LabData.Limits.PidLimit), + NanoCPU: int64(req.Msg.LabData.Limits.CPUCores), + Memory: int64(req.Msg.LabData.Limits.MemoryInMb), }, - JobEntryCmd: req.Msg.EntryCommand, + JobEntryCmd: req.Msg.LabData.EntryCmd, + AutolabCompatible: req.Msg.LabData.AutolabCompatibilityMode, } - labID, err := l.Srv.CreateLab(lab, req.Msg.DockerFile, req.Msg.JobFiles) + labID, err := l.Srv.CreateLab(lab, req.Msg.TmpFolderId) if err != nil { return nil, err } @@ -43,10 +45,12 @@ func (l LabServer) NewLab(ctx context.Context, req *connect.Request[v1.LabReques return res, nil } -func (l LabServer) EditLab(ctx context.Context, req *connect.Request[v1.LabRequest]) (*connect.Response[v1.EditLabResponse], error) { - return nil, fmt.Errorf("unimplmented methods") +func (l LabServer) EditLab(ctx context.Context, c *connect.Request[typesv1.LabData]) (*connect.Response[v1.EditLabResponse], error) { + //TODO implement me + panic("implement me") } func (l LabServer) DeleteLab(ctx context.Context, req *connect.Request[v1.DeleteLabRequest]) (*connect.Response[v1.DeleteLabResponse], error) { + return nil, fmt.Errorf("unimplmented methods") } diff --git a/src/common/config.go b/src/common/config.go index cb3edf4..b6145ee 100644 --- a/src/common/config.go +++ b/src/common/config.go @@ -69,8 +69,19 @@ func InitConfig() { "LABS_DIR", fmt.Sprintf("%s/%s", baseDir, "labs"), ) + tmpUploadFolderPath := setIfEnvPresentOrDefault( + tmpUploadFolder, + "TMP_UPLOAD_DIR", + fmt.Sprintf("%s/%s", baseDir, "tmp_uploads"), + ) - err = makeDirectories([]string{submissionFolderPath, outputFolderPath, sshFolderPath, labsFolderPath}) + err = makeDirectories([]string{ + submissionFolderPath, + outputFolderPath, + sshFolderPath, + labsFolderPath, + tmpUploadFolderPath, + }) if err != nil { log.Fatal().Err(err).Msg("unable to make required directories") } diff --git a/src/common/config_keys.go b/src/common/config_keys.go index 9b88731..14a7fe2 100644 --- a/src/common/config_keys.go +++ b/src/common/config_keys.go @@ -18,6 +18,7 @@ const ( outputDirKey = "folder.job_output_dir" sshDirKey = "folder.ssh_config" labDirKey = "folder.labs" + tmpUploadFolder = "folder.tmp_uploads" // docker config enableLocalDockerKey = "clients.enable_local_docker" @@ -49,6 +50,7 @@ var ( SubmissionFolder = Config{submissionDirKey} OutputFolder = Config{outputDirKey} LabsFolder = Config{labDirKey} + TmpUploadFolder = Config{tmpUploadFolder} // docker config EnableLocalDocker = Config{enableLocalDockerKey} diff --git a/src/common/logger.go b/src/common/logger.go index 4ea2045..ab1f086 100644 --- a/src/common/logger.go +++ b/src/common/logger.go @@ -2,6 +2,7 @@ package common import ( "context" + "fmt" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "gopkg.in/natefinch/lumberjack.v2" @@ -47,6 +48,14 @@ func FileConsoleLogger() zerolog.Logger { ).Level(level) } +// ErrLog logs the original error while returning a sanitized user-facing error. +// +// This hides implementation details from users while ensuring full error information is available for debugging. +func ErrLog(message string, err error, eventLevel *zerolog.Event) error { + eventLevel.Err(err).Msgf(message) + return fmt.Errorf(message) +} + func ConsoleLogger() zerolog.Logger { return getBaseLogger() } diff --git a/src/common/utils.go b/src/common/utils.go new file mode 100644 index 0000000..528303c --- /dev/null +++ b/src/common/utils.go @@ -0,0 +1,126 @@ +package common + +import ( + "fmt" + "github.com/rs/zerolog/log" + "io" + "io/fs" + "os" + "path/filepath" +) + +func FileExists(filename string) bool { + _, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return true +} + +func CloseFile(file io.ReadCloser) { + err := file.Close() + if err != nil { + log.Warn().Err(err).Msg("Error occurred while closing file") + } +} + +// HardLinkFolder creates hard links of all files from source folder to target folder +// and maintains the original UID/GID +func HardLinkFolder(sourceDir, targetDir string) error { + if sourceDir == "" || targetDir == "" { + log.Warn().Msg("Source/target directory is empty") + return nil + } + + sourceStat, err := os.Stat(sourceDir) // Check if source directory exists + if err != nil { + return fmt.Errorf("source directory error: %w", err) + } + + // Create target directory if it doesn't exist + err = os.MkdirAll(targetDir, DefaultFilePerm) + if err != nil { + return fmt.Errorf("failed to create target directory: %w", err) + } + + // Handle based on whether source is file or directory + if !sourceStat.IsDir() { + // For single file, create the parent directory if needed + sourceFile := filepath.Base(sourceDir) + log.Debug().Msgf("Target path is: %s", sourceFile) + + targetDir = fmt.Sprintf("%s/%s", targetDir, sourceFile) + log.Info().Err(err).Msgf("Source is a file, final dest path will be %s", targetDir) + + // Create hard link for the file + if err := createHardLink(sourceDir, targetDir, sourceStat.Mode()); err != nil { + return fmt.Errorf("failed to create hard link from %s to %s: %w", sourceDir, targetDir, err) + } + + return nil + } + + // Walk through the source directory + return filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + // Get file info including system info (UID/GID) + info, err := d.Info() + if err != nil { + return fmt.Errorf("failed to get file info: %w", err) + } + + // Get relative path + relPath, err := filepath.Rel(sourceDir, path) + if err != nil { + return fmt.Errorf("failed to get relative path: %w", err) + } + + // Get target path + targetPath := filepath.Join(targetDir, relPath) + + // If it's a directory, create it in target with proper ownership + if d.IsDir() { + if err := os.MkdirAll(targetPath, DefaultFilePerm); err != nil { + return fmt.Errorf("failed to create directory %s: %w", targetPath, err) + } + + return nil + } + + // Create hard link for files + err = createHardLink(path, targetPath, info.Mode()) + if err != nil { + return fmt.Errorf("failed to create hard link from %s to %s: %w", path, targetPath, err) + } + + return nil + }) +} + +// createHardLink creates a hard link with proper ownership and permissions +func createHardLink(oldPath, newPath string, mode fs.FileMode) error { + // Ensure the target directory exists + targetDir := filepath.Dir(newPath) + if err := os.MkdirAll(targetDir, 0755); err != nil { + return err + } + + // Remove existing file if it exists + if err := os.Remove(newPath); err != nil && !os.IsNotExist(err) { + return err + } + + // Create the hard link + if err := os.Link(oldPath, newPath); err != nil { + return err + } + + if err := os.Chmod(newPath, mode); err != nil { + return fmt.Errorf("failed to set permissions: %w", err) + } + + return nil +} diff --git a/src/generated/docker_rpc/v1/docker.pb.go b/src/generated/docker_rpc/v1/docker.pb.go index c4b7b91..1475768 100644 --- a/src/generated/docker_rpc/v1/docker.pb.go +++ b/src/generated/docker_rpc/v1/docker.pb.go @@ -7,7 +7,7 @@ package v1 import ( - v1 "github.com/makeopensource/leviathan/generated/types/v1" + _ "github.com/makeopensource/leviathan/generated/types/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -565,7 +565,6 @@ func (*NewImageResponse) Descriptor() ([]byte, []int) { type NewImageRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ImageTag string `protobuf:"bytes,1,opt,name=imageTag,proto3" json:"imageTag,omitempty"` - File *v1.FileUpload `protobuf:"bytes,2,opt,name=file,proto3" json:"file,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -607,13 +606,6 @@ func (x *NewImageRequest) GetImageTag() string { return "" } -func (x *NewImageRequest) GetFile() *v1.FileUpload { - if x != nil { - return x.File - } - return nil -} - type ListImageResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Images []*DockerImage `protobuf:"bytes,1,rep,name=images,proto3" json:"images,omitempty"` @@ -978,10 +970,9 @@ const file_docker_rpc_v1_docker_proto_rawDesc = "" + "\n" + "containers\x18\x01 \x03(\v2\x1e.docker_rpc.v1.DockerContainerR\n" + "containers\"\x12\n" + - "\x10NewImageResponse\"W\n" + + "\x10NewImageResponse\"-\n" + "\x0fNewImageRequest\x12\x1a\n" + - "\bimageTag\x18\x01 \x01(\tR\bimageTag\x12(\n" + - "\x04file\x18\x02 \x01(\v2\x14.types.v1.FileUploadR\x04file\"G\n" + + "\bimageTag\x18\x01 \x01(\tR\bimageTag\"G\n" + "\x11ListImageResponse\x122\n" + "\x06images\x18\x01 \x03(\v2\x1a.docker_rpc.v1.DockerImageR\x06images\"\x12\n" + "\x10ListImageRequest\"_\n" + @@ -1048,35 +1039,33 @@ var file_docker_rpc_v1_docker_proto_goTypes = []any{ (*ContainerMetaData)(nil), // 17: docker_rpc.v1.ContainerMetaData (*DockerImage)(nil), // 18: docker_rpc.v1.DockerImage (*ImageMetaData)(nil), // 19: docker_rpc.v1.ImageMetaData - (*v1.FileUpload)(nil), // 20: types.v1.FileUpload } var file_docker_rpc_v1_docker_proto_depIdxs = []int32{ 16, // 0: docker_rpc.v1.ListContainersResponse.containers:type_name -> docker_rpc.v1.DockerContainer - 20, // 1: docker_rpc.v1.NewImageRequest.file:type_name -> types.v1.FileUpload - 18, // 2: docker_rpc.v1.ListImageResponse.images:type_name -> docker_rpc.v1.DockerImage - 17, // 3: docker_rpc.v1.DockerContainer.metadata:type_name -> docker_rpc.v1.ContainerMetaData - 19, // 4: docker_rpc.v1.DockerImage.metadata:type_name -> docker_rpc.v1.ImageMetaData - 6, // 5: docker_rpc.v1.DockerService.CreateContainer:input_type -> docker_rpc.v1.CreateContainerRequest - 8, // 6: docker_rpc.v1.DockerService.DeleteContainer:input_type -> docker_rpc.v1.DeleteContainerRequest - 10, // 7: docker_rpc.v1.DockerService.ListContainers:input_type -> docker_rpc.v1.ListContainersRequest - 0, // 8: docker_rpc.v1.DockerService.StartContainer:input_type -> docker_rpc.v1.StartContainerRequest - 2, // 9: docker_rpc.v1.DockerService.StopContainer:input_type -> docker_rpc.v1.StopContainerRequest - 4, // 10: docker_rpc.v1.DockerService.GetContainerLogs:input_type -> docker_rpc.v1.GetContainerLogRequest - 13, // 11: docker_rpc.v1.DockerService.CreateNewImage:input_type -> docker_rpc.v1.NewImageRequest - 15, // 12: docker_rpc.v1.DockerService.ListImages:input_type -> docker_rpc.v1.ListImageRequest - 7, // 13: docker_rpc.v1.DockerService.CreateContainer:output_type -> docker_rpc.v1.CreateContainerResponse - 9, // 14: docker_rpc.v1.DockerService.DeleteContainer:output_type -> docker_rpc.v1.DeleteContainerResponse - 11, // 15: docker_rpc.v1.DockerService.ListContainers:output_type -> docker_rpc.v1.ListContainersResponse - 1, // 16: docker_rpc.v1.DockerService.StartContainer:output_type -> docker_rpc.v1.StartContainerResponse - 3, // 17: docker_rpc.v1.DockerService.StopContainer:output_type -> docker_rpc.v1.StopContainerResponse - 5, // 18: docker_rpc.v1.DockerService.GetContainerLogs:output_type -> docker_rpc.v1.GetContainerLogResponse - 12, // 19: docker_rpc.v1.DockerService.CreateNewImage:output_type -> docker_rpc.v1.NewImageResponse - 14, // 20: docker_rpc.v1.DockerService.ListImages:output_type -> docker_rpc.v1.ListImageResponse - 13, // [13:21] is the sub-list for method output_type - 5, // [5:13] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 18, // 1: docker_rpc.v1.ListImageResponse.images:type_name -> docker_rpc.v1.DockerImage + 17, // 2: docker_rpc.v1.DockerContainer.metadata:type_name -> docker_rpc.v1.ContainerMetaData + 19, // 3: docker_rpc.v1.DockerImage.metadata:type_name -> docker_rpc.v1.ImageMetaData + 6, // 4: docker_rpc.v1.DockerService.CreateContainer:input_type -> docker_rpc.v1.CreateContainerRequest + 8, // 5: docker_rpc.v1.DockerService.DeleteContainer:input_type -> docker_rpc.v1.DeleteContainerRequest + 10, // 6: docker_rpc.v1.DockerService.ListContainers:input_type -> docker_rpc.v1.ListContainersRequest + 0, // 7: docker_rpc.v1.DockerService.StartContainer:input_type -> docker_rpc.v1.StartContainerRequest + 2, // 8: docker_rpc.v1.DockerService.StopContainer:input_type -> docker_rpc.v1.StopContainerRequest + 4, // 9: docker_rpc.v1.DockerService.GetContainerLogs:input_type -> docker_rpc.v1.GetContainerLogRequest + 13, // 10: docker_rpc.v1.DockerService.CreateNewImage:input_type -> docker_rpc.v1.NewImageRequest + 15, // 11: docker_rpc.v1.DockerService.ListImages:input_type -> docker_rpc.v1.ListImageRequest + 7, // 12: docker_rpc.v1.DockerService.CreateContainer:output_type -> docker_rpc.v1.CreateContainerResponse + 9, // 13: docker_rpc.v1.DockerService.DeleteContainer:output_type -> docker_rpc.v1.DeleteContainerResponse + 11, // 14: docker_rpc.v1.DockerService.ListContainers:output_type -> docker_rpc.v1.ListContainersResponse + 1, // 15: docker_rpc.v1.DockerService.StartContainer:output_type -> docker_rpc.v1.StartContainerResponse + 3, // 16: docker_rpc.v1.DockerService.StopContainer:output_type -> docker_rpc.v1.StopContainerResponse + 5, // 17: docker_rpc.v1.DockerService.GetContainerLogs:output_type -> docker_rpc.v1.GetContainerLogResponse + 12, // 18: docker_rpc.v1.DockerService.CreateNewImage:output_type -> docker_rpc.v1.NewImageResponse + 14, // 19: docker_rpc.v1.DockerService.ListImages:output_type -> docker_rpc.v1.ListImageResponse + 12, // [12:20] is the sub-list for method output_type + 4, // [4:12] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_docker_rpc_v1_docker_proto_init() } diff --git a/src/generated/jobs/v1/jobs.pb.go b/src/generated/jobs/v1/jobs.pb.go index 0a7d98d..356bf19 100644 --- a/src/generated/jobs/v1/jobs.pb.go +++ b/src/generated/jobs/v1/jobs.pb.go @@ -7,7 +7,7 @@ package v1 import ( - v1 "github.com/makeopensource/leviathan/generated/types/v1" + _ "github.com/makeopensource/leviathan/generated/types/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -23,15 +23,11 @@ const ( ) type NewJobRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - JobFiles []*v1.FileUpload `protobuf:"bytes,1,rep,name=jobFiles,proto3" json:"jobFiles,omitempty"` - DockerFile *v1.FileUpload `protobuf:"bytes,2,opt,name=dockerFile,proto3" json:"dockerFile,omitempty"` - ImageName string `protobuf:"bytes,3,opt,name=imageName,proto3" json:"imageName,omitempty"` - JobTimeoutInSeconds uint64 `protobuf:"varint,4,opt,name=jobTimeoutInSeconds,proto3" json:"jobTimeoutInSeconds,omitempty"` - EntryCmd string `protobuf:"bytes,5,opt,name=entryCmd,proto3" json:"entryCmd,omitempty"` - Limits *v1.MachineLimits `protobuf:"bytes,6,opt,name=limits,proto3" json:"limits,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + LabID uint64 `protobuf:"varint,1,opt,name=labID,proto3" json:"labID,omitempty"` + TmpSubmissionFolderId string `protobuf:"bytes,2,opt,name=tmpSubmissionFolderId,proto3" json:"tmpSubmissionFolderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *NewJobRequest) Reset() { @@ -64,48 +60,20 @@ func (*NewJobRequest) Descriptor() ([]byte, []int) { return file_jobs_v1_jobs_proto_rawDescGZIP(), []int{0} } -func (x *NewJobRequest) GetJobFiles() []*v1.FileUpload { +func (x *NewJobRequest) GetLabID() uint64 { if x != nil { - return x.JobFiles - } - return nil -} - -func (x *NewJobRequest) GetDockerFile() *v1.FileUpload { - if x != nil { - return x.DockerFile - } - return nil -} - -func (x *NewJobRequest) GetImageName() string { - if x != nil { - return x.ImageName - } - return "" -} - -func (x *NewJobRequest) GetJobTimeoutInSeconds() uint64 { - if x != nil { - return x.JobTimeoutInSeconds + return x.LabID } return 0 } -func (x *NewJobRequest) GetEntryCmd() string { +func (x *NewJobRequest) GetTmpSubmissionFolderId() string { if x != nil { - return x.EntryCmd + return x.TmpSubmissionFolderId } return "" } -func (x *NewJobRequest) GetLimits() *v1.MachineLimits { - if x != nil { - return x.Limits - } - return nil -} - type NewJobResponse struct { state protoimpl.MessageState `protogen:"open.v1"` JobId string `protobuf:"bytes,1,opt,name=jobId,proto3" json:"jobId,omitempty"` @@ -390,16 +358,10 @@ var File_jobs_v1_jobs_proto protoreflect.FileDescriptor const file_jobs_v1_jobs_proto_rawDesc = "" + "\n" + - "\x12jobs/v1/jobs.proto\x12\ajobs.v1\x1a\x14types/v1/types.proto\"\x94\x02\n" + - "\rNewJobRequest\x120\n" + - "\bjobFiles\x18\x01 \x03(\v2\x14.types.v1.FileUploadR\bjobFiles\x124\n" + - "\n" + - "dockerFile\x18\x02 \x01(\v2\x14.types.v1.FileUploadR\n" + - "dockerFile\x12\x1c\n" + - "\timageName\x18\x03 \x01(\tR\timageName\x120\n" + - "\x13jobTimeoutInSeconds\x18\x04 \x01(\x04R\x13jobTimeoutInSeconds\x12\x1a\n" + - "\bentryCmd\x18\x05 \x01(\tR\bentryCmd\x12/\n" + - "\x06limits\x18\x06 \x01(\v2\x17.types.v1.MachineLimitsR\x06limits\"&\n" + + "\x12jobs/v1/jobs.proto\x12\ajobs.v1\x1a\x14types/v1/types.proto\"[\n" + + "\rNewJobRequest\x12\x14\n" + + "\x05labID\x18\x01 \x01(\x04R\x05labID\x124\n" + + "\x15tmpSubmissionFolderId\x18\x02 \x01(\tR\x15tmpSubmissionFolderId\"&\n" + "\x0eNewJobResponse\x12\x14\n" + "\x05jobId\x18\x01 \x01(\tR\x05jobId\"(\n" + "\x10CancelJobRequest\x12\x14\n" + @@ -443,27 +405,22 @@ var file_jobs_v1_jobs_proto_goTypes = []any{ (*JobLogRequest)(nil), // 4: jobs.v1.JobLogRequest (*JobLogsResponse)(nil), // 5: jobs.v1.JobLogsResponse (*JobStatus)(nil), // 6: jobs.v1.JobStatus - (*v1.FileUpload)(nil), // 7: types.v1.FileUpload - (*v1.MachineLimits)(nil), // 8: types.v1.MachineLimits } var file_jobs_v1_jobs_proto_depIdxs = []int32{ - 7, // 0: jobs.v1.NewJobRequest.jobFiles:type_name -> types.v1.FileUpload - 7, // 1: jobs.v1.NewJobRequest.dockerFile:type_name -> types.v1.FileUpload - 8, // 2: jobs.v1.NewJobRequest.limits:type_name -> types.v1.MachineLimits - 6, // 3: jobs.v1.JobLogsResponse.jobInfo:type_name -> jobs.v1.JobStatus - 0, // 4: jobs.v1.JobService.NewJob:input_type -> jobs.v1.NewJobRequest - 4, // 5: jobs.v1.JobService.GetStatus:input_type -> jobs.v1.JobLogRequest - 4, // 6: jobs.v1.JobService.StreamStatus:input_type -> jobs.v1.JobLogRequest - 2, // 7: jobs.v1.JobService.CancelJob:input_type -> jobs.v1.CancelJobRequest - 1, // 8: jobs.v1.JobService.NewJob:output_type -> jobs.v1.NewJobResponse - 5, // 9: jobs.v1.JobService.GetStatus:output_type -> jobs.v1.JobLogsResponse - 5, // 10: jobs.v1.JobService.StreamStatus:output_type -> jobs.v1.JobLogsResponse - 3, // 11: jobs.v1.JobService.CancelJob:output_type -> jobs.v1.CancelJobResponse - 8, // [8:12] is the sub-list for method output_type - 4, // [4:8] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 6, // 0: jobs.v1.JobLogsResponse.jobInfo:type_name -> jobs.v1.JobStatus + 0, // 1: jobs.v1.JobService.NewJob:input_type -> jobs.v1.NewJobRequest + 4, // 2: jobs.v1.JobService.GetStatus:input_type -> jobs.v1.JobLogRequest + 4, // 3: jobs.v1.JobService.StreamStatus:input_type -> jobs.v1.JobLogRequest + 2, // 4: jobs.v1.JobService.CancelJob:input_type -> jobs.v1.CancelJobRequest + 1, // 5: jobs.v1.JobService.NewJob:output_type -> jobs.v1.NewJobResponse + 5, // 6: jobs.v1.JobService.GetStatus:output_type -> jobs.v1.JobLogsResponse + 5, // 7: jobs.v1.JobService.StreamStatus:output_type -> jobs.v1.JobLogsResponse + 3, // 8: jobs.v1.JobService.CancelJob:output_type -> jobs.v1.CancelJobResponse + 5, // [5:9] is the sub-list for method output_type + 1, // [1:5] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_jobs_v1_jobs_proto_init() } diff --git a/src/generated/labs/v1/labs.pb.go b/src/generated/labs/v1/labs.pb.go index dc4c851..a36d58c 100644 --- a/src/generated/labs/v1/labs.pb.go +++ b/src/generated/labs/v1/labs.pb.go @@ -22,32 +22,28 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type LabRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - LabName string `protobuf:"bytes,1,opt,name=labName,proto3" json:"labName,omitempty"` - EntryCommand string `protobuf:"bytes,2,opt,name=entryCommand,proto3" json:"entryCommand,omitempty"` - TimeLimitInSeconds uint64 `protobuf:"varint,4,opt,name=timeLimitInSeconds,proto3" json:"timeLimitInSeconds,omitempty"` - MachineLimits *v1.MachineLimits `protobuf:"bytes,5,opt,name=machineLimits,proto3" json:"machineLimits,omitempty"` - DockerFile *v1.FileUpload `protobuf:"bytes,6,opt,name=dockerFile,proto3" json:"dockerFile,omitempty"` - JobFiles []*v1.FileUpload `protobuf:"bytes,7,rep,name=jobFiles,proto3" json:"jobFiles,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *LabRequest) Reset() { - *x = LabRequest{} +type NewLabRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TmpFolderId string `protobuf:"bytes,1,opt,name=tmpFolderId,proto3" json:"tmpFolderId,omitempty"` + LabData *v1.LabData `protobuf:"bytes,2,opt,name=labData,proto3" json:"labData,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NewLabRequest) Reset() { + *x = NewLabRequest{} mi := &file_labs_v1_labs_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *LabRequest) String() string { +func (x *NewLabRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*LabRequest) ProtoMessage() {} +func (*NewLabRequest) ProtoMessage() {} -func (x *LabRequest) ProtoReflect() protoreflect.Message { +func (x *NewLabRequest) ProtoReflect() protoreflect.Message { mi := &file_labs_v1_labs_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -59,49 +55,21 @@ func (x *LabRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use LabRequest.ProtoReflect.Descriptor instead. -func (*LabRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use NewLabRequest.ProtoReflect.Descriptor instead. +func (*NewLabRequest) Descriptor() ([]byte, []int) { return file_labs_v1_labs_proto_rawDescGZIP(), []int{0} } -func (x *LabRequest) GetLabName() string { - if x != nil { - return x.LabName - } - return "" -} - -func (x *LabRequest) GetEntryCommand() string { +func (x *NewLabRequest) GetTmpFolderId() string { if x != nil { - return x.EntryCommand + return x.TmpFolderId } return "" } -func (x *LabRequest) GetTimeLimitInSeconds() uint64 { +func (x *NewLabRequest) GetLabData() *v1.LabData { if x != nil { - return x.TimeLimitInSeconds - } - return 0 -} - -func (x *LabRequest) GetMachineLimits() *v1.MachineLimits { - if x != nil { - return x.MachineLimits - } - return nil -} - -func (x *LabRequest) GetDockerFile() *v1.FileUpload { - if x != nil { - return x.DockerFile - } - return nil -} - -func (x *LabRequest) GetJobFiles() []*v1.FileUpload { - if x != nil { - return x.JobFiles + return x.LabData } return nil } @@ -153,7 +121,7 @@ func (x *NewLabResponse) GetLabId() int64 { type EditLabRequest struct { state protoimpl.MessageState `protogen:"open.v1"` LabId int64 `protobuf:"varint,1,opt,name=labId,proto3" json:"labId,omitempty"` - LabInfo *LabRequest `protobuf:"bytes,2,opt,name=labInfo,proto3" json:"labInfo,omitempty"` + LabInfo *v1.LabData `protobuf:"bytes,2,opt,name=labInfo,proto3" json:"labInfo,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -195,7 +163,7 @@ func (x *EditLabRequest) GetLabId() int64 { return 0 } -func (x *EditLabRequest) GetLabInfo() *LabRequest { +func (x *EditLabRequest) GetLabInfo() *v1.LabData { if x != nil { return x.LabInfo } @@ -322,30 +290,23 @@ var File_labs_v1_labs_proto protoreflect.FileDescriptor const file_labs_v1_labs_proto_rawDesc = "" + "\n" + - "\x12labs/v1/labs.proto\x12\alabs.v1\x1a\x14types/v1/types.proto\"\xa1\x02\n" + - "\n" + - "LabRequest\x12\x18\n" + - "\alabName\x18\x01 \x01(\tR\alabName\x12\"\n" + - "\fentryCommand\x18\x02 \x01(\tR\fentryCommand\x12.\n" + - "\x12timeLimitInSeconds\x18\x04 \x01(\x04R\x12timeLimitInSeconds\x12=\n" + - "\rmachineLimits\x18\x05 \x01(\v2\x17.types.v1.MachineLimitsR\rmachineLimits\x124\n" + - "\n" + - "dockerFile\x18\x06 \x01(\v2\x14.types.v1.FileUploadR\n" + - "dockerFile\x120\n" + - "\bjobFiles\x18\a \x03(\v2\x14.types.v1.FileUploadR\bjobFiles\"&\n" + + "\x12labs/v1/labs.proto\x12\alabs.v1\x1a\x14types/v1/types.proto\"^\n" + + "\rNewLabRequest\x12 \n" + + "\vtmpFolderId\x18\x01 \x01(\tR\vtmpFolderId\x12+\n" + + "\alabData\x18\x02 \x01(\v2\x11.types.v1.LabDataR\alabData\"&\n" + "\x0eNewLabResponse\x12\x14\n" + - "\x05labId\x18\x01 \x01(\x03R\x05labId\"U\n" + + "\x05labId\x18\x01 \x01(\x03R\x05labId\"S\n" + "\x0eEditLabRequest\x12\x14\n" + - "\x05labId\x18\x01 \x01(\x03R\x05labId\x12-\n" + - "\alabInfo\x18\x02 \x01(\v2\x13.labs.v1.LabRequestR\alabInfo\"\x11\n" + + "\x05labId\x18\x01 \x01(\x03R\x05labId\x12+\n" + + "\alabInfo\x18\x02 \x01(\v2\x11.types.v1.LabDataR\alabInfo\"\x11\n" + "\x0fEditLabResponse\"(\n" + "\x10DeleteLabRequest\x12\x14\n" + "\x05LabID\x18\x01 \x01(\x03R\x05LabID\"\x13\n" + - "\x11DeleteLabResponse2\xc8\x01\n" + + "\x11DeleteLabResponse2\xc9\x01\n" + "\n" + - "LabService\x128\n" + - "\x06NewLab\x12\x13.labs.v1.LabRequest\x1a\x17.labs.v1.NewLabResponse\"\x00\x12:\n" + - "\aEditLab\x12\x13.labs.v1.LabRequest\x1a\x18.labs.v1.EditLabResponse\"\x00\x12D\n" + + "LabService\x12;\n" + + "\x06NewLab\x12\x16.labs.v1.NewLabRequest\x1a\x17.labs.v1.NewLabResponse\"\x00\x128\n" + + "\aEditLab\x12\x11.types.v1.LabData\x1a\x18.labs.v1.EditLabResponse\"\x00\x12D\n" + "\tDeleteLab\x12\x19.labs.v1.DeleteLabRequest\x1a\x1a.labs.v1.DeleteLabResponse\"\x00B\x8c\x01\n" + "\vcom.labs.v1B\tLabsProtoP\x01Z5github.com/makeopensource/leviathan/generated/labs/v1\xa2\x02\x03LXX\xaa\x02\aLabs.V1\xca\x02\aLabs\\V1\xe2\x02\x13Labs\\V1\\GPBMetadata\xea\x02\bLabs::V1b\x06proto3" @@ -363,31 +324,28 @@ func file_labs_v1_labs_proto_rawDescGZIP() []byte { var file_labs_v1_labs_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_labs_v1_labs_proto_goTypes = []any{ - (*LabRequest)(nil), // 0: labs.v1.LabRequest + (*NewLabRequest)(nil), // 0: labs.v1.NewLabRequest (*NewLabResponse)(nil), // 1: labs.v1.NewLabResponse (*EditLabRequest)(nil), // 2: labs.v1.EditLabRequest (*EditLabResponse)(nil), // 3: labs.v1.EditLabResponse (*DeleteLabRequest)(nil), // 4: labs.v1.DeleteLabRequest (*DeleteLabResponse)(nil), // 5: labs.v1.DeleteLabResponse - (*v1.MachineLimits)(nil), // 6: types.v1.MachineLimits - (*v1.FileUpload)(nil), // 7: types.v1.FileUpload + (*v1.LabData)(nil), // 6: types.v1.LabData } var file_labs_v1_labs_proto_depIdxs = []int32{ - 6, // 0: labs.v1.LabRequest.machineLimits:type_name -> types.v1.MachineLimits - 7, // 1: labs.v1.LabRequest.dockerFile:type_name -> types.v1.FileUpload - 7, // 2: labs.v1.LabRequest.jobFiles:type_name -> types.v1.FileUpload - 0, // 3: labs.v1.EditLabRequest.labInfo:type_name -> labs.v1.LabRequest - 0, // 4: labs.v1.LabService.NewLab:input_type -> labs.v1.LabRequest - 0, // 5: labs.v1.LabService.EditLab:input_type -> labs.v1.LabRequest - 4, // 6: labs.v1.LabService.DeleteLab:input_type -> labs.v1.DeleteLabRequest - 1, // 7: labs.v1.LabService.NewLab:output_type -> labs.v1.NewLabResponse - 3, // 8: labs.v1.LabService.EditLab:output_type -> labs.v1.EditLabResponse - 5, // 9: labs.v1.LabService.DeleteLab:output_type -> labs.v1.DeleteLabResponse - 7, // [7:10] is the sub-list for method output_type - 4, // [4:7] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 6, // 0: labs.v1.NewLabRequest.labData:type_name -> types.v1.LabData + 6, // 1: labs.v1.EditLabRequest.labInfo:type_name -> types.v1.LabData + 0, // 2: labs.v1.LabService.NewLab:input_type -> labs.v1.NewLabRequest + 6, // 3: labs.v1.LabService.EditLab:input_type -> types.v1.LabData + 4, // 4: labs.v1.LabService.DeleteLab:input_type -> labs.v1.DeleteLabRequest + 1, // 5: labs.v1.LabService.NewLab:output_type -> labs.v1.NewLabResponse + 3, // 6: labs.v1.LabService.EditLab:output_type -> labs.v1.EditLabResponse + 5, // 7: labs.v1.LabService.DeleteLab:output_type -> labs.v1.DeleteLabResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_labs_v1_labs_proto_init() } diff --git a/src/generated/labs/v1/v1connect/labs.connect.go b/src/generated/labs/v1/v1connect/labs.connect.go index d01c01a..4820918 100644 --- a/src/generated/labs/v1/v1connect/labs.connect.go +++ b/src/generated/labs/v1/v1connect/labs.connect.go @@ -9,6 +9,7 @@ import ( context "context" errors "errors" v1 "github.com/makeopensource/leviathan/generated/labs/v1" + v11 "github.com/makeopensource/leviathan/generated/types/v1" http "net/http" strings "strings" ) @@ -43,8 +44,8 @@ const ( // LabServiceClient is a client for the labs.v1.LabService service. type LabServiceClient interface { - NewLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.NewLabResponse], error) - EditLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.EditLabResponse], error) + NewLab(context.Context, *connect.Request[v1.NewLabRequest]) (*connect.Response[v1.NewLabResponse], error) + EditLab(context.Context, *connect.Request[v11.LabData]) (*connect.Response[v1.EditLabResponse], error) DeleteLab(context.Context, *connect.Request[v1.DeleteLabRequest]) (*connect.Response[v1.DeleteLabResponse], error) } @@ -59,13 +60,13 @@ func NewLabServiceClient(httpClient connect.HTTPClient, baseURL string, opts ... baseURL = strings.TrimRight(baseURL, "/") labServiceMethods := v1.File_labs_v1_labs_proto.Services().ByName("LabService").Methods() return &labServiceClient{ - newLab: connect.NewClient[v1.LabRequest, v1.NewLabResponse]( + newLab: connect.NewClient[v1.NewLabRequest, v1.NewLabResponse]( httpClient, baseURL+LabServiceNewLabProcedure, connect.WithSchema(labServiceMethods.ByName("NewLab")), connect.WithClientOptions(opts...), ), - editLab: connect.NewClient[v1.LabRequest, v1.EditLabResponse]( + editLab: connect.NewClient[v11.LabData, v1.EditLabResponse]( httpClient, baseURL+LabServiceEditLabProcedure, connect.WithSchema(labServiceMethods.ByName("EditLab")), @@ -82,18 +83,18 @@ func NewLabServiceClient(httpClient connect.HTTPClient, baseURL string, opts ... // labServiceClient implements LabServiceClient. type labServiceClient struct { - newLab *connect.Client[v1.LabRequest, v1.NewLabResponse] - editLab *connect.Client[v1.LabRequest, v1.EditLabResponse] + newLab *connect.Client[v1.NewLabRequest, v1.NewLabResponse] + editLab *connect.Client[v11.LabData, v1.EditLabResponse] deleteLab *connect.Client[v1.DeleteLabRequest, v1.DeleteLabResponse] } // NewLab calls labs.v1.LabService.NewLab. -func (c *labServiceClient) NewLab(ctx context.Context, req *connect.Request[v1.LabRequest]) (*connect.Response[v1.NewLabResponse], error) { +func (c *labServiceClient) NewLab(ctx context.Context, req *connect.Request[v1.NewLabRequest]) (*connect.Response[v1.NewLabResponse], error) { return c.newLab.CallUnary(ctx, req) } // EditLab calls labs.v1.LabService.EditLab. -func (c *labServiceClient) EditLab(ctx context.Context, req *connect.Request[v1.LabRequest]) (*connect.Response[v1.EditLabResponse], error) { +func (c *labServiceClient) EditLab(ctx context.Context, req *connect.Request[v11.LabData]) (*connect.Response[v1.EditLabResponse], error) { return c.editLab.CallUnary(ctx, req) } @@ -104,8 +105,8 @@ func (c *labServiceClient) DeleteLab(ctx context.Context, req *connect.Request[v // LabServiceHandler is an implementation of the labs.v1.LabService service. type LabServiceHandler interface { - NewLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.NewLabResponse], error) - EditLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.EditLabResponse], error) + NewLab(context.Context, *connect.Request[v1.NewLabRequest]) (*connect.Response[v1.NewLabResponse], error) + EditLab(context.Context, *connect.Request[v11.LabData]) (*connect.Response[v1.EditLabResponse], error) DeleteLab(context.Context, *connect.Request[v1.DeleteLabRequest]) (*connect.Response[v1.DeleteLabResponse], error) } @@ -151,11 +152,11 @@ func NewLabServiceHandler(svc LabServiceHandler, opts ...connect.HandlerOption) // UnimplementedLabServiceHandler returns CodeUnimplemented from all methods. type UnimplementedLabServiceHandler struct{} -func (UnimplementedLabServiceHandler) NewLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.NewLabResponse], error) { +func (UnimplementedLabServiceHandler) NewLab(context.Context, *connect.Request[v1.NewLabRequest]) (*connect.Response[v1.NewLabResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("labs.v1.LabService.NewLab is not implemented")) } -func (UnimplementedLabServiceHandler) EditLab(context.Context, *connect.Request[v1.LabRequest]) (*connect.Response[v1.EditLabResponse], error) { +func (UnimplementedLabServiceHandler) EditLab(context.Context, *connect.Request[v11.LabData]) (*connect.Response[v1.EditLabResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("labs.v1.LabService.EditLab is not implemented")) } diff --git a/src/generated/types/v1/types.pb.go b/src/generated/types/v1/types.pb.go index 3341561..d7dd8f1 100644 --- a/src/generated/types/v1/types.pb.go +++ b/src/generated/types/v1/types.pb.go @@ -21,28 +21,31 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type FileUpload struct { - state protoimpl.MessageState `protogen:"open.v1"` - Filename string `protobuf:"bytes,1,opt,name=filename,proto3" json:"filename,omitempty"` - Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *FileUpload) Reset() { - *x = FileUpload{} +type LabData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Labname string `protobuf:"bytes,1,opt,name=labname,proto3" json:"labname,omitempty"` + EntryCmd string `protobuf:"bytes,2,opt,name=entryCmd,proto3" json:"entryCmd,omitempty"` + JobTimeoutInSeconds uint64 `protobuf:"varint,3,opt,name=jobTimeoutInSeconds,proto3" json:"jobTimeoutInSeconds,omitempty"` + AutolabCompatibilityMode bool `protobuf:"varint,4,opt,name=autolabCompatibilityMode,proto3" json:"autolabCompatibilityMode,omitempty"` + Limits *MachineLimits `protobuf:"bytes,5,opt,name=limits,proto3" json:"limits,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LabData) Reset() { + *x = LabData{} mi := &file_types_v1_types_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *FileUpload) String() string { +func (x *LabData) String() string { return protoimpl.X.MessageStringOf(x) } -func (*FileUpload) ProtoMessage() {} +func (*LabData) ProtoMessage() {} -func (x *FileUpload) ProtoReflect() protoreflect.Message { +func (x *LabData) ProtoReflect() protoreflect.Message { mi := &file_types_v1_types_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -54,21 +57,42 @@ func (x *FileUpload) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use FileUpload.ProtoReflect.Descriptor instead. -func (*FileUpload) Descriptor() ([]byte, []int) { +// Deprecated: Use LabData.ProtoReflect.Descriptor instead. +func (*LabData) Descriptor() ([]byte, []int) { return file_types_v1_types_proto_rawDescGZIP(), []int{0} } -func (x *FileUpload) GetFilename() string { +func (x *LabData) GetLabname() string { + if x != nil { + return x.Labname + } + return "" +} + +func (x *LabData) GetEntryCmd() string { if x != nil { - return x.Filename + return x.EntryCmd } return "" } -func (x *FileUpload) GetContent() []byte { +func (x *LabData) GetJobTimeoutInSeconds() uint64 { if x != nil { - return x.Content + return x.JobTimeoutInSeconds + } + return 0 +} + +func (x *LabData) GetAutolabCompatibilityMode() bool { + if x != nil { + return x.AutolabCompatibilityMode + } + return false +} + +func (x *LabData) GetLimits() *MachineLimits { + if x != nil { + return x.Limits } return nil } @@ -137,11 +161,13 @@ var File_types_v1_types_proto protoreflect.FileDescriptor const file_types_v1_types_proto_rawDesc = "" + "\n" + - "\x14types/v1/types.proto\x12\btypes.v1\"B\n" + - "\n" + - "FileUpload\x12\x1a\n" + - "\bfilename\x18\x01 \x01(\tR\bfilename\x12\x18\n" + - "\acontent\x18\x02 \x01(\fR\acontent\"g\n" + + "\x14types/v1/types.proto\x12\btypes.v1\"\xde\x01\n" + + "\aLabData\x12\x18\n" + + "\alabname\x18\x01 \x01(\tR\alabname\x12\x1a\n" + + "\bentryCmd\x18\x02 \x01(\tR\bentryCmd\x120\n" + + "\x13jobTimeoutInSeconds\x18\x03 \x01(\x04R\x13jobTimeoutInSeconds\x12:\n" + + "\x18autolabCompatibilityMode\x18\x04 \x01(\bR\x18autolabCompatibilityMode\x12/\n" + + "\x06limits\x18\x05 \x01(\v2\x17.types.v1.MachineLimitsR\x06limits\"g\n" + "\rMachineLimits\x12\x1a\n" + "\bCPUCores\x18\x01 \x01(\x05R\bCPUCores\x12\x1e\n" + "\n" + @@ -165,15 +191,16 @@ func file_types_v1_types_proto_rawDescGZIP() []byte { var file_types_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_types_v1_types_proto_goTypes = []any{ - (*FileUpload)(nil), // 0: types.v1.FileUpload + (*LabData)(nil), // 0: types.v1.LabData (*MachineLimits)(nil), // 1: types.v1.MachineLimits } var file_types_v1_types_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: types.v1.LabData.limits:type_name -> types.v1.MachineLimits + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_types_v1_types_proto_init() } diff --git a/src/models/lab.go b/src/models/lab.go index 9408a90..6acc669 100644 --- a/src/models/lab.go +++ b/src/models/lab.go @@ -7,13 +7,14 @@ import ( type Lab struct { gorm.Model - Name string `gorm:"unique"` - JobTimeout time.Duration - ImageTag string - DockerFilePath string - JobLimits MachineLimits `gorm:"embedded;embeddedPrefix:machine_limit_"` - JobEntryCmd string - JobFilesDirPath string + Name string + JobTimeout time.Duration + ImageTag string + DockerFilePath string + JobLimits MachineLimits `gorm:"embedded;embeddedPrefix:machine_limit_"` + JobEntryCmd string + JobFilesDirPath string + AutolabCompatible bool } // VerifyJobLimits checks if job limits are provided, diff --git a/src/service/docker/docker_client.go b/src/service/docker/docker_client.go index 4b4f27f..382d8e2 100644 --- a/src/service/docker/docker_client.go +++ b/src/service/docker/docker_client.go @@ -119,15 +119,12 @@ func (c *DkClient) ListContainers(machineId string) ([]*dktypes.ContainerMetaDat // CreateNewContainer creates a new container from given image func (c *DkClient) CreateNewContainer(jobUuid, image, jobFolder, entryCmd string, machineLimits container.Resources) (string, error) { - baseCmd := fmt.Sprintf("cd /home/%s", jobFolder) - runCommand := fmt.Sprintf("%s && %s", baseCmd, entryCmd) - config := &container.Config{ Image: image, Labels: map[string]string{ "con": jobUuid, }, - Cmd: []string{"sh", "-c", runCommand}, + Cmd: []string{"sh", "-c", entryCmd}, } hostConfig := &container.HostConfig{ diff --git a/src/service/docker/docker_utils_ssh.go b/src/service/docker/docker_utils_ssh.go index 68dbce5..2ee01bf 100644 --- a/src/service/docker/docker_utils_ssh.go +++ b/src/service/docker/docker_utils_ssh.go @@ -9,7 +9,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "github.com/makeopensource/leviathan/common" + . "github.com/makeopensource/leviathan/common" "github.com/makeopensource/leviathan/models" "github.com/rs/zerolog/log" "github.com/spf13/viper" @@ -70,7 +70,7 @@ func saveHostKey(machine models.MachineOptions) func(hostname string, remote net } func writeMachineToConfigFile(machine models.MachineOptions) { - machineKey := fmt.Sprintf("%s.%s", common.ClientsSSH.ConfigKey, machine.Name()) + machineKey := fmt.Sprintf("%s.%s", ClientsSSH.ConfigKey, machine.Name()) viper.Set(machineKey, machine) err := viper.WriteConfig() if err != nil { @@ -109,7 +109,7 @@ func GenerateKeyPair() (privateKey []byte, publicKey []byte, err error) { // // the generated keys can be found in common.SSHConfigFolder func initKeyPairFile() { - basePath := common.SSHConfigFolder.GetStr() + basePath := SSHConfigFolder.GetStr() privateKeyPath := fmt.Sprintf("%s/%s", basePath, "id_rsa") publicKeyPath := fmt.Sprintf("%s/%s", basePath, "id_rsa.pub") @@ -120,7 +120,7 @@ func initKeyPairFile() { Str("private_key_file", privateKeyPath). Str("public_key_file", publicKeyPath) - if fileExists(privateKeyPath) && fileExists(publicKeyPath) { + if FileExists(privateKeyPath) && FileExists(publicKeyPath) { logF.Msg("found existing keys... skipping generation") return } @@ -140,18 +140,10 @@ func initKeyPairFile() { logF.Msg("Generated new SSH key pair") } -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} - func LoadPrivateKey() ([]byte, error) { return os.ReadFile(fmt.Sprintf( "%s/%s", - common.SSHConfigFolder.GetStr(), + SSHConfigFolder.GetStr(), "id_rsa", )) } diff --git a/src/service/file_manager/file_manager_service.go b/src/service/file_manager/file_manager_service.go new file mode 100644 index 0000000..8866662 --- /dev/null +++ b/src/service/file_manager/file_manager_service.go @@ -0,0 +1,156 @@ +package file_manager + +import ( + "fmt" + "github.com/google/uuid" + . "github.com/makeopensource/leviathan/common" + "github.com/rs/zerolog/log" + "io" + "os" + "path/filepath" +) + +const ( + JobDataFolderName = "jobdata" + DockerfileName = "Dockerfile" +) + +type FileManagerService struct{} + +func NewFileManagerService() *FileManagerService { + return &FileManagerService{} +} + +type FileInfo struct { + Reader io.ReadCloser + Filename string +} + +// CreateTmpLabFolder lab files will be stored here +func (f *FileManagerService) CreateTmpLabFolder(dockerfile io.Reader, jobFiles ...*FileInfo) (string, error) { + folderUUID, basePath, err := f.createBaseFolder() + if err != nil { + return "", err + } + + jobDataDir := filepath.Join(basePath, JobDataFolderName) + err = os.MkdirAll(jobDataDir, os.ModePerm) + if err != nil { + return "", ErrLog("unable to create job data folder", err, log.Error()) + } + + if err = f.SaveFile(basePath, DockerfileName, dockerfile); err != nil { + return "", err + } + + for _, jobFile := range jobFiles { + if err = f.SaveFile(jobDataDir, jobFile.Filename, jobFile.Reader); err != nil { + return "", err + } + } + + return folderUUID, nil +} + +// CreateSubmissionFolder submission made to grader will be places here +func (f *FileManagerService) CreateSubmissionFolder(jobFiles ...*FileInfo) (string, error) { + folderUUID, basePath, err := f.createBaseFolder() + if err != nil { + return "", err + } + + for _, jobFile := range jobFiles { + if err = f.SaveFile(basePath, jobFile.Filename, jobFile.Reader); err != nil { + return "", err + } + } + + return folderUUID, nil +} + +func (f *FileManagerService) createBaseFolder() (string, string, error) { + folderUUID, err := uuid.NewUUID() + if err != nil { + return "", "", ErrLog("Unable to generate uuid", err, log.Error()) + } + stringUuid := folderUUID.String() + basePath := filepath.Join(TmpUploadFolder.GetStr(), stringUuid) + + err = os.Mkdir(basePath, DefaultFilePerm) + if err != nil { + return "", "", ErrLog("Unable to create tmp folder", err, log.Error()) + } + + return folderUUID.String(), basePath, nil +} + +func (f *FileManagerService) SaveFile(basePath string, filename string, file io.Reader) error { + fPath := filepath.Join(basePath, filename) + // Create the file + dst, err := os.Create(fPath) + + if err != nil { + return ErrLog( + "Failed to create destination file", + err, + log.Error(), + ) + } + defer func(dst *os.File) { + err := dst.Close() + if err != nil { + log.Warn().Err(err).Msg("Error occurred while closing file") + } + }(dst) + + // Copy the file contents + written, err := io.Copy(dst, file) + if err != nil { + return ErrLog( + "Failed to write file", + err, + log.Error(), + ) + } + + log.Debug().Int64("size", written).Msgf( + "File: %s uploaded successfully", + filename, + ) + + return nil +} + +func (f *FileManagerService) DeleteFolder(folderUuid string) { + basePath := filepath.Join(TmpUploadFolder.GetStr(), folderUuid) + if err := os.RemoveAll(basePath); err != nil { + log.Warn().Err(err).Msgf("failed to delete tmp folder %s", folderUuid) + } +} + +func (f *FileManagerService) GetLabFilePaths(folderUuid string) (basePath string, err error) { + basePath = filepath.Join(TmpUploadFolder.GetStr(), folderUuid) + jobData := filepath.Join(basePath, JobDataFolderName) + dockerFile := filepath.Join(basePath, DockerfileName) + + if _, err = f.checkFolder(jobData); err != nil { + return "", err + } + if _, err = f.checkFolder(dockerFile); err != nil { + return "", err + } + + return basePath, err +} + +func (f *FileManagerService) GetSubmissionPath(uuid string) (string, error) { + path := filepath.Join(TmpUploadFolder.GetStr(), uuid) + return f.checkFolder(path) +} + +func (f *FileManagerService) checkFolder(path string) (jobData string, err error) { + if !FileExists(path) { + return "", fmt.Errorf("could not find path") + } + return path, err +} diff --git a/src/service/jobs/job_queue.go b/src/service/jobs/job_queue.go index 0451d32..bd0606e 100644 --- a/src/service/jobs/job_queue.go +++ b/src/service/jobs/job_queue.go @@ -45,8 +45,7 @@ func (q *JobQueue) AddJob(mes *models.Job) error { jog(mes.JobCtx).Info().Msg("sending job to queue") err := mes.ValidateForQueue() if err != nil { - jog(mes.JobCtx).Err(err).Msg("job validation failed") - return err + return common.ErrLog("job validation failed: "+err.Error(), err, jog(mes.JobCtx).Error()) } // run in go routine in case queue is full and gets blocked diff --git a/src/service/jobs/job_service.go b/src/service/jobs/job_service.go index a260a2d..2bdf9e7 100644 --- a/src/service/jobs/job_service.go +++ b/src/service/jobs/job_service.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "github.com/google/uuid" - com "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" + . "github.com/makeopensource/leviathan/common" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/docker" + "github.com/makeopensource/leviathan/service/file_manager" "github.com/makeopensource/leviathan/service/labs" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -23,30 +23,40 @@ type JobService struct { queue *JobQueue broadcastCh *models.BroadcastChannel labSrv *labs.LabService + fileManSrv *file_manager.FileManagerService } -func NewJobService(db *gorm.DB, bc *models.BroadcastChannel, dockerService *docker.DkService, labService *labs.LabService) *JobService { - return &JobService{ +func NewJobService( + db *gorm.DB, + bc *models.BroadcastChannel, + dockerService *docker.DkService, + labService *labs.LabService, + tmpFileService *file_manager.FileManagerService, +) *JobService { + srv := &JobService{ db: db, broadcastCh: bc, dockerSrv: dockerService, - queue: NewJobQueue(uint(com.ConcurrentJobs.GetUint64()), db, dockerService), + queue: NewJobQueue(uint(ConcurrentJobs.GetUint64()), db, dockerService), labSrv: labService, + fileManSrv: tmpFileService, } -} - -func NewJobServiceWithDeps(dockerService *docker.DkService, labService *labs.LabService) *JobService { - db, bc := com.InitDB() - srv := NewJobService(db, bc, dockerService, labService) srv.cleanupOrphanJobs() return srv } -func (job *JobService) NewJobFromRPC(newJob *models.Job, jobFiles []*v1.FileUpload, dockerfile *v1.FileUpload) (string, error) { +func (job *JobService) NewJob(newJob *models.Job, submissionFolderId string) (string, error) { + if newJob.LabData == nil { + labData, err := job.getLab(newJob.LabID) + if err != nil { + return "", err + } + newJob.LabData = labData + } + jobId, err := uuid.NewUUID() if err != nil { - log.Error().Err(err).Msg("failed to generate job ID") - return "", fmt.Errorf("failed to generate job ID") + return "", ErrLog("failed to generate job ID", err, log.Error()) } newJob.JobId = jobId.String() @@ -55,87 +65,27 @@ func (job *JobService) NewJobFromRPC(newJob *models.Job, jobFiles []*v1.FileUplo // job context, so that it can be cancelled, and store sub logger ctx := job.queue.NewJobContext(newJob.JobId) - // "" implies randomly named tmp folder - dockerFileDir, err := CreateTmpJobDir(newJob.JobId, "", dockerfile) - if err != nil { - jog(ctx).Error().Err(err).Msg("failed to create dockerfile dir") - return "", fmt.Errorf("failed to create dockerfile dir") - } - - jobDir, err := CreateTmpJobDir(newJob.JobId, com.SubmissionFolder.GetStr(), jobFiles...) + jobDir, err := CreateTmpJobDir(newJob.JobId, SubmissionFolder.GetStr()) if err != nil { - jog(ctx).Error().Err(err).Msg("failed to create job dir") - return "", fmt.Errorf("failed to create job dir") + return "", ErrLog("failed to create job dir", err, jog(ctx).Error()) } - logPath, err, reason := setupLogFile(newJob.JobId) - if err != nil { - jog(ctx).Error().Err(err).Str("reason", reason).Msg("failed to setup log file") - return "", fmt.Errorf("failed to setup log file") - } - - // setup job metadata - newJob.MachineId = mId - newJob.Status = models.Queued - newJob.OutputLogFilePath = logPath - newJob.TmpJobFolderPath = jobDir - newJob.LabData.DockerFilePath = fmt.Sprintf("%s/%s", dockerFileDir, dockerfile.Filename) - newJob.JobCtx = ctx - newJob.LabData.VerifyJobLimits() - jog(newJob.JobCtx).Debug().Any("limits", newJob.LabData.JobLimits).Msg("job limits") - - res := job.db.Create(newJob) - if res.Error != nil { - jog(ctx).Error().Err(res.Error).Msg("Failed to save job to db") - return "", fmt.Errorf("failed to save job to db") - } - - err = job.queue.AddJob(newJob) + submissionFolder, err := job.fileManSrv.GetSubmissionPath(submissionFolderId) if err != nil { return "", err } + defer job.fileManSrv.DeleteFolder(submissionFolderId) - return jobId.String(), nil -} - -func (job *JobService) NewJobFromLab(newJob *models.Job, studentFiles []*v1.FileUpload) (string, error) { - if newJob.LabID == 0 { - return "", fmt.Errorf("invalid lab ID %d", newJob.LabID) - } - - labData, err := job.labSrv.GetLabFromDB(newJob.LabID) - if err != nil { - return "", err - } - newJob.LabData = labData - - jobId, err := uuid.NewUUID() - if err != nil { - log.Error().Err(err).Msg("failed to generate job ID") - return "", fmt.Errorf("failed to generate job ID") - } - newJob.JobId = jobId.String() - - mId := job.dockerSrv.ClientManager.GetLeastJobCountMachineId() - - // job context, so that it can be cancelled, and store sub logger - ctx := job.queue.NewJobContext(newJob.JobId) - - jobDir, err := CreateTmpJobDir(newJob.JobId, com.SubmissionFolder.GetStr(), studentFiles...) - if err != nil { - jog(ctx).Error().Err(err).Msg("failed to create job dir") - return "", fmt.Errorf("failed to create job dir") + if err = HardLinkFolder(submissionFolder, jobDir); err != nil { + return "", ErrLog("unable to copy files to job dir", err, log.Error()) } - if err = HardLinkFolder(newJob.LabData.JobFilesDirPath, jobDir); err != nil { - jog(ctx).Error().Err(err).Msg("unable to copy files to job dir") - return "", fmt.Errorf("unable to copy files to job dir") + return "", ErrLog("unable to copy files to job dir", err, log.Error()) } logPath, err, reason := setupLogFile(newJob.JobId) if err != nil { - jog(ctx).Error().Err(err).Str("reason", reason).Msg("failed to setup log file") - return "", fmt.Errorf("failed to setup log file") + return "", ErrLog("failed to setup log file: "+reason, err, jog(ctx).Error().Str("reason", reason)) } // setup job metadata @@ -149,8 +99,7 @@ func (job *JobService) NewJobFromLab(newJob *models.Job, studentFiles []*v1.File res := job.db.Create(newJob) if res.Error != nil { - jog(ctx).Error().Err(res.Error).Msg("Failed to save job to db") - return "", fmt.Errorf("failed to save job to db") + return "", ErrLog("failed to save job to db", res.Error, jog(ctx).Error()) } err = job.queue.AddJob(newJob) @@ -271,13 +220,13 @@ func (job *JobService) ListenToJobLogs(ctx context.Context, jobInfo *models.Job) content := ReadLogFile(jobInfo.OutputLogFilePath) contLen := len(content) if contLen > prevLength { // send if content changed - log.Debug().Str(com.JobLogKey, jobInfo.JobId).Msgf("sending log, length changed from %d to %d", prevLength, contLen) + log.Debug().Str(JobLogKey, jobInfo.JobId).Msgf("sending log, length changed from %d to %d", prevLength, contLen) prevLength = contLen logChannel <- content } case <-ctx.Done(): close(logChannel) - log.Debug().Str(com.JobLogKey, jobInfo.JobId).Msg("stopping listening for logs") + log.Debug().Str(JobLogKey, jobInfo.JobId).Msg("stopping listening for logs") return } } @@ -301,7 +250,7 @@ func (job *JobService) checkJob(jobUuid string) (*models.Job, bool, string, erro } if jobInf.Status.Done() { - log.Debug().Str(com.JobLogKey, jobUuid).Msg("job is already done") + log.Debug().Str(JobLogKey, jobUuid).Msg("job is already done") content := ReadLogFile(jobInf.OutputLogFilePath) return jobInf, true, content, nil @@ -325,7 +274,7 @@ func (job *JobService) getJobFromDB(jobUuid string) (*models.Job, error) { // for example machine running leviathan shutdown unexpectedly or leviathan had an unrecoverable error func (job *JobService) cleanupOrphanJobs() { var orphanJobs []*models.Job - res := job.db. + res := job.db.Preload("LabData"). Where("status = ?", string(models.Queued)). Or("status = ?", string(models.Running)). Or("status = ?", string(models.Preparing)). @@ -380,7 +329,7 @@ func (job *JobService) cleanupOrphanJobs() { // setupLogFile store grader output func setupLogFile(jobId string) (string, error, string) { - outputFile := fmt.Sprintf("%s/%s.txt", com.OutputFolder.GetStr(), jobId) + outputFile := fmt.Sprintf("%s/%s.txt", OutputFolder.GetStr(), jobId) outFile, err := os.Create(outputFile) if err != nil { return "", err, fmt.Sprintf("error while creating log file at %s", outputFile) @@ -400,6 +349,17 @@ func setupLogFile(jobId string) (string, error, string) { return full, nil, "" } +func (job *JobService) getLab(labId uint) (*models.Lab, error) { + if labId == 0 { + return nil, fmt.Errorf("invalid lab ID %d", labId) + } + labData, err := job.labSrv.GetLabFromDB(labId) + if err != nil { + return nil, err + } + return labData, nil +} + // zerolog log with context, intended for job logs // // shorthand for log.Ctx(ctx) diff --git a/src/service/jobs/job_service_tango_test.go b/src/service/jobs/job_service_tango_test.go new file mode 100644 index 0000000..fc59007 --- /dev/null +++ b/src/service/jobs/job_service_tango_test.go @@ -0,0 +1,231 @@ +package jobs + +import ( + "github.com/makeopensource/leviathan/models" + . "github.com/makeopensource/leviathan/service/file_manager" + "os" + "path/filepath" + "testing" + "time" +) + +var ( + basePath = "../../../example/tango" + tangoDockerFile = basePath + "/tango-Dockerfile" + autolab0 = basePath + "/tango0" + autolab1 = basePath + "/tango1" + autolab2 = basePath + "/tango2" + autolab3 = basePath + "/tango3" + autolab4 = basePath + "/tango4" + tangoTimeout = 10 * time.Second +) + +type testMap = map[string]testCase + +var ( + tangoTestCases = map[string]testMap{ + "tango0": { + "correct": { + studentFile: autolab0 + "/handin.py", + expectedOutput: `{"scores": {"q1": 10, "q2": 10, "q3": 10}}`, + correctStatus: models.Complete, + }, + "cheating1": testCase{ + studentFile: autolab0 + "/handin_cheating1.py", + expectedOutput: `{"scores": {"q1": 0, "q2": 0, "q3": 0}}`, + correctStatus: models.Complete, + }, + "cheating2": testCase{ + studentFile: autolab0 + "/handin_cheating2.py", + expectedOutput: `Maximum timeout reached for job, job ran for 10s`, + correctStatus: models.Failed, + }, + "incorrect1": testCase{ + studentFile: autolab0 + "/handin_incorrect1.py", + expectedOutput: `{"scores": {"q1": 0, "q2": 0, "q3": 0}}`, + correctStatus: models.Complete, + }, + "incorrect2": testCase{ + studentFile: autolab0 + "/handin_incorrect2.py", + expectedOutput: `unable to parse log output`, + correctStatus: models.Failed, + }, + }, + "tango1": { + "correct": { + studentFile: autolab1 + "/handin.py", + expectedOutput: `{"scores": {"q1": 10, "q2": 10, "q3": 10}}`, + correctStatus: models.Complete, + }, + "incorrect1": testCase{ + studentFile: autolab1 + "/handin_incorrect1.py", + expectedOutput: `{"scores": {"q1": 10, "q2": 0, "q3": 0}}`, + correctStatus: models.Complete, + }, + "incorrect2": testCase{ + studentFile: autolab1 + "/handin_incorrect2.py", + expectedOutput: `{"scores": {"q1": 9, "q2": 3, "q3": 3}}`, + correctStatus: models.Complete, + }, + "incorrect3": testCase{ + studentFile: autolab1 + "/handin_incorrect3.py", + expectedOutput: `{"scores": {"q1": 1, "q2": 0, "q3": 0}}`, + correctStatus: models.Complete, + }, + "incorrect4": testCase{ + studentFile: autolab1 + "/handin_incorrect4.py", + expectedOutput: `{"scores": {"q1": 0, "q2": 0, "q3": 0}}`, + correctStatus: models.Complete, + }, + }, + "tango3": { + "correct": { + studentFile: autolab3 + "/handin.json", + expectedOutput: `{"scores": {"q1": 10, "q2": 10, "q3": 99}}`, + correctStatus: models.Complete, + }, + }, + "tango4": { + "correct": { + studentFile: autolab4 + "/handin.py", + expectedOutput: `{"scores": {"q1": 10}}`, + correctStatus: models.Complete, + }, + "incorrect": { + studentFile: autolab4 + "/handin_incorrect.py", + expectedOutput: `{"scores": {"q1": 0}}`, + correctStatus: models.Complete, + }, + }, + } + tangoTestFuncs = map[string]func(*testing.T){ + "autolab0": TestTango0, + "autolab1": TestTango1, + "autolab3": TestTango3, + "autolab4": TestTango4, + } +) + +func TestAllTango(t *testing.T) { + for tCase, test := range tangoTestFuncs { + t.Run(tCase, func(t *testing.T) { + t.Parallel() + test(t) + }) + } +} + +func TestTango0(t *testing.T) { + setupTest() + + folderName := "tango0" + cases := tangoTestCases[folderName] + + labId := newLab(t, autolab0) + + for name, test := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + jobid := setupJobProcessTango(t, labId, test.studentFile) + testJob(t, jobid, test.expectedOutput, test.correctStatus) + }) + } +} + +func TestTango1(t *testing.T) { + setupTest() + + folderName := "tango1" + cases := tangoTestCases[folderName] + labId := newLab(t, autolab1) + + for name, test := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + jobid := setupJobProcessTango(t, labId, test.studentFile) + testJob(t, jobid, test.expectedOutput, test.correctStatus) + }) + } +} + +func TestTango3(t *testing.T) { + setupTest() + + folderName := "tango3" + cases := tangoTestCases[folderName] + labId := newLab(t, autolab3) + + for name, test := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + jobid := setupJobProcessTango(t, labId, test.studentFile) + testJob(t, jobid, test.expectedOutput, test.correctStatus) + }) + } +} + +func TestTango4(t *testing.T) { + setupTest() + + folderName := "tango4" + cases := tangoTestCases[folderName] + labId := newLab(t, autolab4) + + for name, test := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + jobid := setupJobProcessTango(t, labId, test.studentFile) + testJob(t, jobid, test.expectedOutput, test.correctStatus) + }) + } +} + +func newLab(t *testing.T, folderName string) uint { + tarPath := folderName + "/autograde.tar" + tangoMakeFilePath := folderName + "/Makefile" + labId := createLab(t, &models.Lab{ + Name: "tango-test-lab", + JobTimeout: tangoTimeout, + AutolabCompatible: true, + }, tangoDockerFile, tarPath, tangoMakeFilePath) + if labId == 0 { + t.Fatalf("Failed to create lab") + } + return labId +} + +func setupJobProcessTango(t *testing.T, labId uint, studentCodePath string) string { + newJob := &models.Job{LabID: labId} + + studentFileName := filepath.Base(studentCodePath) + if filepath.Ext(studentFileName) == ".json" { + studentFileName = "handin.json" + } else { + studentFileName = "handin.py" + } + + studentCode, err := os.Open(studentCodePath) + if err != nil { + t.Fatal(err) + } + + folderId, err := fileManTestService.CreateSubmissionFolder(&FileInfo{ + Reader: studentCode, + Filename: studentFileName, + }) + if err != nil { + t.Fatal(err) + return "" + } + + jobId, err := jobTestService.NewJob( + newJob, + folderId, + ) + + if err != nil { + t.Fatal("unable to create job", err) + } + + return jobId +} diff --git a/src/service/jobs/job_service_test.go b/src/service/jobs/job_service_test.go index fddd955..0142ac1 100644 --- a/src/service/jobs/job_service_test.go +++ b/src/service/jobs/job_service_test.go @@ -3,9 +3,9 @@ package jobs import ( "fmt" "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/docker" + . "github.com/makeopensource/leviathan/service/file_manager" "github.com/makeopensource/leviathan/service/labs" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -19,13 +19,16 @@ import ( ) var ( - dkTestService *docker.DkService - jobTestService *JobService - setupOnce sync.Once + dkTestService *docker.DkService + jobTestService *JobService + labTestService *labs.LabService + fileManTestService *FileManagerService + setupOnce sync.Once + labCreateOnce sync.Once + createLabId uint ) const ( - imageName = "arithmetic-python" dockerFilePath = "../../../example/simple-addition/ex-Dockerfile" makeFilePath = "../../../example/simple-addition/makefile" graderFilePath = "../../../example/simple-addition/grader.py" @@ -141,7 +144,7 @@ func TestCancel(t *testing.T) { timeout := testCases["timeout"] timeout.expectedOutput = "Job was cancelled" - jobId := setupJobProcess(timeout.studentFile, timeLimit) + jobId := setupJobProcess(t, timeout.studentFile, timeLimit) // cancel the job after 3 seconds time.AfterFunc(3*time.Second, func() { @@ -158,66 +161,87 @@ func TestCancel(t *testing.T) { } func testJobProcessor(t *testing.T, studentCodePath string, correctOutput string, timeout time.Duration, status models.JobStatus) { - jobId := setupJobProcess(studentCodePath, timeout) + jobId := setupJobProcess(t, studentCodePath, timeout) testJob(t, jobId, correctOutput, status) } -func setupJobProcess(studentCodePath string, timeout time.Duration) string { - graderBytes, err := os.ReadFile(graderFilePath) - if err != nil { - log.Fatal().Err(err).Msg("Error reading grader.py") +func setupJobProcess(t *testing.T, studentCodePath string, timeout time.Duration) string { + labId := setupLab(t, &models.Lab{ + Name: "test-lab", + JobTimeout: timeout, + JobEntryCmd: "make grade", + AutolabCompatible: false, + }, dockerFilePath, graderFilePath, makeFilePath) + if labId == 0 { + t.Fatalf("Failed to create lab") } - makefileBytes, err := os.ReadFile(makeFilePath) + studentBytes, err := os.Open(studentCodePath) if err != nil { - log.Fatal().Err(err).Msg("Error reading grader.py") + t.Fatal("Error reading student", err) + } + studentFileInfo := &FileInfo{ + Reader: studentBytes, + Filename: "student.py", } - studentBytes, err := os.ReadFile(studentCodePath) + tmpSubmissionFolder, err := fileManTestService.CreateSubmissionFolder(studentFileInfo) if err != nil { - log.Fatal().Err(err).Msg("Error reading student") + return "" } - dockerBytes, err := os.ReadFile(dockerFilePath) + newJob := &models.Job{LabID: labId} + jobId, err := jobTestService.NewJob( + newJob, + tmpSubmissionFolder, + ) + if err != nil { - log.Fatal().Err(err).Msg("Error reading docker file") + t.Fatal("Error creating job", err) + return "" } - newLab := models.Lab{ - JobTimeout: timeout, - JobEntryCmd: "make grade", - ImageTag: imageName, + return jobId +} + +func createLab(t *testing.T, labData *models.Lab, dockerfilePath string, files ...string) uint { + var labfiles []*FileInfo + + for _, file := range files { + filename := filepath.Base(file) + fileBytes, err := os.Open(file) + if err != nil { + t.Fatal("Error reading ", filename, " ", err) + } + + labfiles = append(labfiles, &FileInfo{ + Reader: fileBytes, + Filename: filename, + }) } - newJob := &models.Job{LabData: &newLab} + dockerfile, err := os.Open(dockerfilePath) + if err != nil { + t.Fatalf("Error reading docker file: %v", err) + } - jobId, err := jobTestService.NewJobFromRPC( - newJob, - []*v1.FileUpload{ - { - Filename: filepath.Base(makeFilePath), - Content: makefileBytes, - }, - { - Filename: filepath.Base(graderFilePath), - Content: graderBytes, - }, - { - Filename: "student.py", - Content: studentBytes, - }, - }, - &v1.FileUpload{ - Filename: filepath.Base(dockerFilePath), - Content: dockerBytes, - }, - ) + tmpFolderID, err := fileManTestService.CreateTmpLabFolder(dockerfile, labfiles...) + if err != nil { + t.Fatalf("Error creating tmp folder: %v", err) + } + labId, err := labTestService.CreateLab(labData, tmpFolderID) if err != nil { - log.Fatal().Err(err).Msgf("Error creating job") + t.Fatalf("Error creating lab: %v", err) } + return labId +} - return jobId +func setupLab(t *testing.T, labData *models.Lab, dockerfilePath string, files ...string) uint { + labCreateOnce.Do(func() { + createLabId = createLab(t, labData, dockerfilePath, files...) + }) + return createLabId } func testJob(t *testing.T, jobId string, correctOutput string, correctStatus models.JobStatus) { @@ -227,7 +251,7 @@ func testJob(t *testing.T, jobId string, correctOutput string, correctStatus mod return } - t.Log("Job ID: ", jobId, " Logs: %s", logs) + t.Log("Job ID: ", jobId, " Logs:\n", logs) returned := strings.TrimSpace(jobInfo.StatusMessage) expected := strings.TrimSpace(correctOutput) @@ -244,12 +268,12 @@ func setupTest() { func initServices() { common.InitConfig() - db, bc := common.InitDB() dkTestService = docker.NewDockerServiceWithClients() - lab := labs.NewLabService(db, dkTestService) - jobTestService = NewJobService(db, bc, dkTestService, lab) + fileManTestService = NewFileManagerService() + labTestService = labs.NewLabService(db, dkTestService, fileManTestService) + jobTestService = NewJobService(db, bc, dkTestService, labTestService, fileManTestService) // no logs on tests log.Logger = log.Logger.Level(zerolog.Disabled) diff --git a/src/service/jobs/job_utils.go b/src/service/jobs/job_utils.go index 880d9ce..5f3cad4 100644 --- a/src/service/jobs/job_utils.go +++ b/src/service/jobs/job_utils.go @@ -3,131 +3,20 @@ package jobs import ( "encoding/json" "fmt" - "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" "github.com/rs/zerolog/log" - "io/fs" "os" - "path/filepath" ) -// HardLinkFolder creates hard links of all files from source folder to target folder -// and maintains the original UID/GID -func HardLinkFolder(sourceDir, targetDir string) error { - // Check if source directory exists - sourceStat, err := os.Stat(sourceDir) - if err != nil { - return fmt.Errorf("source directory error: %w", err) - } - - // Create target directory if it doesn't exist - err = os.MkdirAll(targetDir, common.DefaultFilePerm) - if err != nil { - return fmt.Errorf("failed to create target directory: %w", err) - } - - // Handle based on whether source is file or directory - if !sourceStat.IsDir() { - // For single file, create the parent directory if needed - sourceFile := filepath.Base(sourceDir) - log.Debug().Msgf("Target path is: %s", sourceFile) - - targetDir = fmt.Sprintf("%s/%s", targetDir, sourceFile) - log.Info().Err(err).Msgf("Source is a file, final dest path will be %s", targetDir) - - // Create hard link for the file - if err := createHardLink(sourceDir, targetDir, sourceStat.Mode()); err != nil { - return fmt.Errorf("failed to create hard link from %s to %s: %w", sourceDir, targetDir, err) - } - - return nil - } - - // Walk through the source directory - return filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - // Get file info including system info (UID/GID) - info, err := d.Info() - if err != nil { - return fmt.Errorf("failed to get file info: %w", err) - } - - // Get relative path - relPath, err := filepath.Rel(sourceDir, path) - if err != nil { - return fmt.Errorf("failed to get relative path: %w", err) - } - - // Get target path - targetPath := filepath.Join(targetDir, relPath) - - // If it's a directory, create it in target with proper ownership - if d.IsDir() { - if err := os.MkdirAll(targetPath, common.DefaultFilePerm); err != nil { - return fmt.Errorf("failed to create directory %s: %w", targetPath, err) - } - - return nil - } - - // Create hard link for files - err = createHardLink(path, targetPath, info.Mode()) - if err != nil { - return fmt.Errorf("failed to create hard link from %s to %s: %w", path, targetPath, err) - } - - return nil - }) -} - -// createHardLink creates a hard link with proper ownership and permissions -func createHardLink(oldPath, newPath string, mode fs.FileMode) error { - // Ensure the target directory exists - targetDir := filepath.Dir(newPath) - if err := os.MkdirAll(targetDir, 0755); err != nil { - return err - } - - // Remove existing file if it exists - if err := os.Remove(newPath); err != nil && !os.IsNotExist(err) { - return err - } - - // Create the hard link - if err := os.Link(oldPath, newPath); err != nil { - return err - } - - if err := os.Chmod(newPath, mode); err != nil { - return fmt.Errorf("failed to set permissions: %w", err) - } - - return nil -} - // CreateTmpJobDir sets up a throwaway dir to store submission files // you might be wondering why the '/autolab' subdir, TarDir untars it under its parent dir, // so in container this will unpack with 'autolab' as the parent folder // why not modify TarDir I tried and, this was easier than modifying whatever is going in that function -func CreateTmpJobDir(uuid, baseFolder string, files ...*v1.FileUpload) (string, error) { +func CreateTmpJobDir(uuid, baseFolder string) (string, error) { tmpFolder, err := createJobFolder(uuid, baseFolder) if err != nil { return "", err } - for _, file := range files { - if err := os.WriteFile( - fmt.Sprintf("%s/%s", tmpFolder, file.Filename), - file.Content, - common.DefaultFilePerm, - ); err != nil { - return "", err - } - } - return tmpFolder, nil } diff --git a/src/service/labs/lab_service.go b/src/service/labs/lab_service.go index 6b585dd..cf33da1 100644 --- a/src/service/labs/lab_service.go +++ b/src/service/labs/lab_service.go @@ -2,64 +2,66 @@ package labs import ( "fmt" - com "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" + . "github.com/makeopensource/leviathan/common" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/docker" + . "github.com/makeopensource/leviathan/service/file_manager" "github.com/rs/zerolog/log" "gorm.io/gorm" "os" - "strconv" + "path/filepath" "strings" ) type LabService struct { - db *gorm.DB - dk *docker.DkService + db *gorm.DB + dk *docker.DkService + fileMan *FileManagerService } -func NewLabService(db *gorm.DB, dk *docker.DkService) *LabService { +func NewLabService(db *gorm.DB, dk *docker.DkService, service *FileManagerService) *LabService { return &LabService{ - db: db, - dk: dk, + db: db, + dk: dk, + fileMan: service, } } -func (service *LabService) CreateLab(lab *models.Lab, dockerFile *v1.FileUpload, jobFiles []*v1.FileUpload) (uint, error) { - // first save to check lab name uniqueness - lab, err := service.SaveLabToDB(lab) +func (service *LabService) CreateLab(lab *models.Lab, jobDirId string) (uint, error) { + tmpDir, err := service.fileMan.GetLabFilePaths(jobDirId) if err != nil { return 0, err } - - basePath := fmt.Sprintf("%s/%s", com.LabsFolder.GetStr(), strconv.Itoa(int(lab.ID))) - jobDataDirPath := basePath + "/jobData" - - if err = os.MkdirAll(jobDataDirPath, com.DefaultFilePerm); err != nil { - log.Error().Err(err).Msgf("unable to create directories for lab: %s", lab.Name) - return 0, fmt.Errorf("unable to create directories for lab: %s", lab.Name) + defer service.fileMan.DeleteFolder(jobDirId) + + jobFolderName := fmt.Sprintf("%s_%s", lab.Name, jobDirId) + jobDataDirPath := fmt.Sprintf("%s/%s", LabsFolder.GetStr(), jobFolderName) + if err = os.MkdirAll(jobDataDirPath, DefaultFilePerm); err != nil { + return 0, ErrLog( + "unable to create directories for lab: "+lab.Name, + err, + log.Error(), + ) } - for _, file := range jobFiles { - path := fmt.Sprintf("%s/%s", jobDataDirPath, file.Filename) - if err := writeFile(path, file.Content); err != nil { - return 0, err - } + if err = HardLinkFolder(tmpDir, jobDataDirPath); err != nil { + return 0, ErrLog("unable to copy files to job dir", err, log.Error()) } + lab.DockerFilePath = filepath.Join(jobDataDirPath, DockerfileName) + lab.JobFilesDirPath = filepath.Join(jobDataDirPath, JobDataFolderName) + lab.ImageTag = fmt.Sprintf("%s:v1", lab.Name) lab.ImageTag = strings.ToLower(strings.Trim(strings.TrimSpace(lab.ImageTag), " ")) - dockerFile.Filename = fmt.Sprintf("%s_%s", lab.Name, dockerFile.Filename) - dockerFilePath := fmt.Sprintf("%s/%s", basePath, dockerFile.Filename) - - if err = writeFile(dockerFilePath, dockerFile.Content); err != nil { - return 0, err + if lab.AutolabCompatible { + lab.JobEntryCmd = CreateTangoEntryCommand( + WithTimeout(int(lab.JobTimeout.Seconds())), + ) + } else { + lab.JobEntryCmd = CreateLeviathanEntryCommand(lab.JobEntryCmd) } - lab.DockerFilePath = dockerFilePath - lab.JobFilesDirPath = jobDataDirPath - // final save to update paths db, err := service.SaveLabToDB(lab) if err != nil { @@ -69,36 +71,68 @@ func (service *LabService) CreateLab(lab *models.Lab, dockerFile *v1.FileUpload, return db.ID, nil } -func (service *LabService) EditLab(id uint, lab *models.Lab) error { - panic("implement me") +func (service *LabService) EditLab(id uint, lab *models.Lab, jobFiles string) (uint, error) { + labData, err := service.GetLabFromDB(id) + if err != nil { + return 0, err + } + + err = service.deleteLabFiles(labData) + if err != nil { + return 0, err + } + + _, err = service.CreateLab(lab, jobFiles) + if err != nil { + return 0, err + } + + return labData.ID, nil } func (service *LabService) DeleteLab(id uint) error { + labData, err := service.GetLabFromDB(id) + if err != nil { + return err + } + + err = service.deleteLabFiles(labData) + if err != nil { + return err + } + if res := service.db.Delete(&models.Lab{}, id); res.Error != nil { return res.Error } + + return nil +} + +func (service *LabService) deleteLabFiles(labData *models.Lab) error { + err := os.RemoveAll(filepath.Base(labData.DockerFilePath)) + if err != nil { + return ErrLog( + "unable to delete directories for lab: "+labData.Name, + err, + log.Error(), + ) + } return nil } func (service *LabService) GetLabFromDB(id uint) (*models.Lab, error) { var lab models.Lab - if res := service.db.First(&lab).Where("ID = ?", id); res.Error != nil { + if res := service.db.Where("ID = ?", id).First(&lab); res.Error != nil { return nil, res.Error } return &lab, nil } func (service *LabService) SaveLabToDB(lab *models.Lab) (*models.Lab, error) { - if res := service.db.Save(lab); res.Error != nil { + res := service.db.Save(lab) + if res.Error != nil { return nil, res.Error } - return lab, nil -} -func writeFile(path string, fileData []byte) error { - return os.WriteFile( - path, - fileData, - com.DefaultFilePerm, - ) + return lab, nil } diff --git a/src/service/labs/lab_service_test.go b/src/service/labs/lab_service_test.go index e6eac92..cf5d302 100644 --- a/src/service/labs/lab_service_test.go +++ b/src/service/labs/lab_service_test.go @@ -2,10 +2,9 @@ package labs import ( "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" "github.com/makeopensource/leviathan/models" "github.com/makeopensource/leviathan/service/docker" - "github.com/makeopensource/leviathan/service/jobs" + . "github.com/makeopensource/leviathan/service/file_manager" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "os" @@ -17,13 +16,12 @@ import ( var ( dkTestService *docker.DkService - jobTestService *jobs.JobService + fileMan *FileManagerService labTestService *LabService setupOnce sync.Once ) const ( - imageName = "arithmetic-python" dockerFilePath = "../../../example/simple-addition/ex-Dockerfile" makeFilePath = "../../../example/simple-addition/makefile" graderFilePath = "../../../example/simple-addition/grader.py" @@ -32,36 +30,37 @@ const ( func TestLabService_CreateLab(t *testing.T) { initDeps() - graderBytes, err := os.ReadFile(graderFilePath) + graderBytes, err := os.Open(graderFilePath) if err != nil { t.Fatalf("Error reading grader.py: %v", err) return } - makefileBytes, err := os.ReadFile(makeFilePath) + makefileBytes, err := os.Open(makeFilePath) if err != nil { - t.Fatalf("Error reading grader.py: %v", err) + t.Fatalf("Error reading makefile: %v", err) return } - dockerBytes, err := os.ReadFile(dockerFilePath) + dockerfile, err := os.Open(dockerFilePath) if err != nil { t.Fatalf("Error reading docker file: %v", err) return } - jobFiles := []*v1.FileUpload{ + files := []*FileInfo{ { + Reader: makefileBytes, Filename: filepath.Base(makeFilePath), - Content: makefileBytes, }, { + Reader: graderBytes, Filename: filepath.Base(graderFilePath), - Content: graderBytes, }, } - dockerFile := &v1.FileUpload{ - Filename: filepath.Base(dockerFilePath), - Content: dockerBytes, + tmpFolderID, err := fileMan.CreateTmpLabFolder(dockerfile, files...) + if err != nil { + t.Fatalf("Error creating tmp folder: %v", err) + return } lab := models.Lab{ @@ -71,23 +70,38 @@ func TestLabService_CreateLab(t *testing.T) { JobEntryCmd: "make grade", } - createLab, err := labTestService.CreateLab(&lab, dockerFile, jobFiles) + createLab, err := labTestService.CreateLab(&lab, tmpFolderID) if err != nil { t.Fatalf("Error creating lab: %v", err) return } t.Logf("Created Lab: %v", createLab) + + labDta, err := labTestService.GetLabFromDB(createLab) + if err != nil { + t.Fatalf("Error retrieving lab: %v", err) + return + } + + if !common.FileExists(labDta.JobFilesDirPath) { + t.Fatalf("Job files dir does not exist") + return + } + if !common.FileExists(labDta.DockerFilePath) { + t.Fatalf("Dockerfile does not exist") + return + } } func initDeps() { setupOnce.Do(func() { common.InitConfig() - db, bc := common.InitDB() + db, _ := common.InitDB() dkTestService = docker.NewDockerServiceWithClients() - labTestService = NewLabService(db, dkTestService) - jobTestService = jobs.NewJobService(db, bc, dkTestService, labTestService) + fileMan = NewFileManagerService() + labTestService = NewLabService(db, dkTestService, fileMan) // no logs on tests log.Logger = log.Logger.Level(zerolog.Disabled) diff --git a/src/service/labs/lab_service_utils_test.go b/src/service/labs/lab_service_utils_test.go new file mode 100644 index 0000000..286c6db --- /dev/null +++ b/src/service/labs/lab_service_utils_test.go @@ -0,0 +1 @@ +package labs diff --git a/src/service/labs/lab_utils.go b/src/service/labs/lab_utils.go new file mode 100644 index 0000000..da71814 --- /dev/null +++ b/src/service/labs/lab_utils.go @@ -0,0 +1,59 @@ +package labs + +import "fmt" + +// LimitOption is a function type that modifies AutodriverLimits +type LimitOption func(*AutodriverLimits) + +// AutodriverLimits limits to construct the autodriver cli args +// more info - https://github.com/UB-CSE-IT/Autolab-Public-Documentation/blob/d6c2fb902d8cc0534794f20d6ef0bab871fe2602/The%20autograding%20process.md#running-the-job +// todo verify assumptions +type AutodriverLimits struct { + // limits the number of processes that can be started + ULimit int + // sets the maximum file size that can be created in bytes I assume + MaxFileSize int + // limits the size of the output in bytes I assume + MaxOutputSize int + // sets the job timeout in seconds I assume + Timeout int +} + +var ( + // DefaultAutoDriverLimits limits taken from + // https://github.com/UB-CSE-IT/Autolab-Public-Documentation/blob/d6c2fb902d8cc0534794f20d6ef0bab871fe2602/The%20autograding%20process.md#running-the-job + DefaultAutoDriverLimits = AutodriverLimits{ + ULimit: 100, + MaxFileSize: 104857600, + Timeout: 20, + MaxOutputSize: 1024000, + } +) + +// CreateLeviathanEntryCommand command for normal graders made for leviathan +func CreateLeviathanEntryCommand(cmd string) string { + return "cd autolab;" + cmd +} + +// CreateTangoEntryCommand builds the autodriver command for tango compatibility +// defaults to DefaultAutoDriverLimits, if no limits is passed +func CreateTangoEntryCommand(opts ...LimitOption) string { + limits := &DefaultAutoDriverLimits // Start with default limits + for _, opt := range opts { // Apply all options + opt(limits) + } + + return fmt.Sprintf(""+ + "chown autolab:autolab autolab; chmod 755 autolab;"+ // mode perm since CopyToContainer switches to root and messes up perms + "su autolab -c \"autodriver -u %d -f %d -t %d -o %d autolab > output/feedback 2>&1\";"+ + "cat output/feedback;", // print to stdout so that leviathan can parse + limits.ULimit, limits.MaxFileSize, limits.Timeout, limits.MaxOutputSize, + ) +} + +// WithTimeout changes the DefaultAutoDriverLimits timeout +func WithTimeout(timeout int) LimitOption { + return func(limits *AutodriverLimits) { + limits.Timeout = timeout + } +} diff --git a/src/service/service.go b/src/service/service.go index a8fc328..aab2f27 100644 --- a/src/service/service.go +++ b/src/service/service.go @@ -3,14 +3,18 @@ package service import ( "github.com/makeopensource/leviathan/common" "github.com/makeopensource/leviathan/service/docker" + "github.com/makeopensource/leviathan/service/file_manager" "github.com/makeopensource/leviathan/service/jobs" "github.com/makeopensource/leviathan/service/labs" ) func InitServices() (*docker.DkService, *jobs.JobService, *labs.LabService) { db, bc := common.InitDB() + dkService := docker.NewDockerServiceWithClients() - labService := labs.NewLabService(db, dkService) - jobService := jobs.NewJobService(db, bc, dkService, labService) + fileManService := file_manager.NewFileManagerService() + labService := labs.NewLabService(db, dkService, fileManService) + jobService := jobs.NewJobService(db, bc, dkService, labService, fileManService) + return dkService, jobService, labService } diff --git a/src/tests/job_lab_test.go b/src/tests/job_lab_test.go deleted file mode 100644 index 1840c1c..0000000 --- a/src/tests/job_lab_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package tests - -import ( - "github.com/makeopensource/leviathan/common" - v1 "github.com/makeopensource/leviathan/generated/types/v1" - "github.com/makeopensource/leviathan/models" - "github.com/makeopensource/leviathan/service/docker" - "github.com/makeopensource/leviathan/service/jobs" - "github.com/makeopensource/leviathan/service/labs" - "github.com/stretchr/testify/assert" - "log" - "os" - "path/filepath" - "strings" - "sync" - "testing" - "time" -) - -var ( - dkTestService *docker.DkService - jobTestService *jobs.JobService - labTestService *labs.LabService - setupOnce sync.Once -) - -const ( - imageName = "arithmetic-python" - dockerFilePath = "../../example/simple-addition/ex-Dockerfile" - makeFilePath = "../../example/simple-addition/makefile" - graderFilePath = "../../example/simple-addition/grader.py" -) - -// creates a lab and send a job -func TestLabJob(t *testing.T) { - err := os.RemoveAll("appdata") - if err != nil { - t.Fatal(err) - } - initDeps() - - labId := createLab(t) - testJobProcessor(t, "../../example/simple-addition/student_correct.py", labId) -} - -func testJobProcessor(t *testing.T, studentCodePath string, labId uint) { - jobId := setupJobProcess(studentCodePath, labId) - testJob(t, jobId, "", "") -} - -func setupJobProcess(studentCodePath string, labId uint) string { - studentBytes, err := os.ReadFile(studentCodePath) - if err != nil { - log.Fatal("Error reading student", err) - } - - newJob := &models.Job{LabID: labId} - jobId, err := jobTestService.NewJobFromLab( - newJob, - []*v1.FileUpload{ - { - Filename: "student.py", - Content: studentBytes, - }, - }, - ) - if err != nil { - log.Fatal("Error creating job", err) - } - - return jobId -} - -func testJob(t *testing.T, jobId string, correctOutput string, correctStatus models.JobStatus) { - jobInfo, logs, err := jobTestService.WaitForJobAndLogs(jobId) - if err != nil { - t.Fatalf("Error waiting for job: %v", err) - return - } - - t.Log("Job ID: ", jobId, " Logs: ", logs) - - returned := strings.TrimSpace(jobInfo.StatusMessage) - expected := strings.TrimSpace(correctOutput) - - assert.Equal(t, expected, returned) - assert.Equal(t, correctStatus, jobInfo.Status) -} - -func createLab(t *testing.T) uint { - graderBytes, err := os.ReadFile(graderFilePath) - if err != nil { - t.Fatalf("Error reading grader.py: %v", err) - return 0 - } - makefileBytes, err := os.ReadFile(makeFilePath) - if err != nil { - t.Fatalf("Error reading grader.py: %v", err) - return 0 - } - dockerBytes, err := os.ReadFile(dockerFilePath) - if err != nil { - t.Fatalf("Error reading docker file: %v", err) - return 0 - } - - jobFiles := []*v1.FileUpload{ - { - Filename: filepath.Base(makeFilePath), - Content: makefileBytes, - }, - { - Filename: filepath.Base(graderFilePath), - Content: graderBytes, - }, - } - - dockerFile := &v1.FileUpload{ - Filename: filepath.Base(dockerFilePath), - Content: dockerBytes, - } - - lab := models.Lab{ - Name: "test-lab", - JobTimeout: time.Second * 10, - JobEntryCmd: "make grade", - } - - createLab, err := labTestService.CreateLab(&lab, dockerFile, jobFiles) - if err != nil { - t.Fatalf("Error creating lab: %v", err) - return 0 - } - - t.Logf("Created Lab: %v", createLab) - return createLab -} - -func initDeps() { - setupOnce.Do(func() { - common.InitConfig() - db, bc := common.InitDB() - - dkTestService = docker.NewDockerServiceWithClients() - labTestService = labs.NewLabService(db, dkTestService) - jobTestService = jobs.NewJobService(db, bc, dkTestService, labTestService) - - // no logs on tests - //log.Logger = log.Logger.Level(zerolog.Disabled) - }) -}