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?
+
+
+ Yes
+
+
+
+ No
+
+
+
+
+
+
+ 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
+
+
+ Language:
+
+ Python
+ Java
+ C++
+
+
+
+
+
+ Max Inputs:
+
+
+
+
+
+ Sources:
+
+
+
+
+
+ Upload your code here:
+
+
+
\ 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
-
+
-
+
\ 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)
- })
-}