diff --git a/backend/.gitignore b/backend/.gitignore index 9cb1cf159..369409424 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -12,6 +12,9 @@ __pycache__ .log tmp/ +# Frontend, which we serve from Django +reactapp/ + # Script for debugging purposes. root/scripts/testing.py diff --git a/backend/poetry.lock b/backend/poetry.lock index f91628950..e72e216a9 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "asgiref" @@ -1253,7 +1253,22 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "whitenoise" +version = "6.11.0" +description = "Radically simplified static file serving for WSGI applications" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258"}, + {file = "whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f"}, +] + +[package.extras] +brotli = ["brotli"] + [metadata] lock-version = "2.1" python-versions = "3.11.2" -content-hash = "73bd547fdcdd415bccbf83e63b88a0ac29342270c5f600632b9dc0b5292b5316" +content-hash = "982d70cc6750677a76bda1b21d951a080c6d1e1d5e47ba9e84e4f3b337a27afa" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index b4ba5b67d..937665d9e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -154,6 +154,7 @@ pillow = "11.*" gunicorn = "23.*" django-admin-autocomplete-filter = "0.*" psycopg = { extras = ["c"], version = "*" } +whitenoise = "^6.11.0" [tool.poetry.group.dev.dependencies] diff --git a/backend/root/settings/base.py b/backend/root/settings/base.py index 658be2341..c933f4c54 100644 --- a/backend/root/settings/base.py +++ b/backend/root/settings/base.py @@ -23,6 +23,9 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent +# React frontend +REACT_BUILD_DIR = "reactapp" + IS_DOCKER = os.environ.get('IS_DOCKER') == 'yes' # Load '.env'. @@ -40,7 +43,12 @@ # Static STATIC_ROOT = BASE_DIR / 'staticroot' -STATIC_URL = '/static/' +STATIC_URL = '/assets/' + +STATICFILES_DIRS = [ + BASE_DIR / REACT_BUILD_DIR, + BASE_DIR / REACT_BUILD_DIR / 'assets', +] # Media MEDIA_ROOT = BASE_DIR / 'mediaroot' @@ -93,6 +101,7 @@ MIDDLEWARE = [ 'root.custom_classes.middlewares.RequestLogMiddleware', 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/backend/samfundet/routing/__init__.py b/backend/samfundet/routing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/samfundet/routing/frontend_routes.py b/backend/samfundet/routing/frontend_routes.py new file mode 100644 index 000000000..f3e09441c --- /dev/null +++ b/backend/samfundet/routing/frontend_routes.py @@ -0,0 +1,96 @@ +""" +THIS FILE IS AUTOGENERATED! +DO NOT WRITE IN THIS FILE, AS IT WILL BE OVERWRITTEN ON NEXT UPDATE. + +Frontend routes meant for consumption by the Django backend. + +Run 'yarn export-routes' in the frontend directory to regenerate +""" + +HOME = "/" +GANGS = "/gangs/" +HEALTH = "/health/" +ABOUT = "/about/" +VENUES = "/venues/" +LOGIN = "/login/" +NEW_LOGIN = "/new-login/" +SIGNUP = "/signup/" +EVENTS = "/events/" +EVENT = "/events//" +INFORMATION_PAGE_LIST = "/information/" +INFORMATION_PAGE_DETAIL = "/information//" +SAKSDOKUMENTER = "/saksdokumenter/" +MEMBERSHIP = "/membership" +LUKA = "/luka" +CONTRIBUTORS = "/contributors" +RECRUITMENT = "/recruitment/" +RECRUITMENT_APPLICATION = "/recruitment//position//" +RECRUITMENT_APPLICATION_OVERVIEW = "/recruitment//my-applications/" +ORGANIZATION_RECRUITMENT = "/recruitment//" +CONTACT = "/contact" +PURCHASE_CALLBACK = "/purchase-callback/" +LYCHE = "/lyche/" +SULTEN = "/lyche" +SULTEN_MENU = "/lyche/menu/" +SULTEN_RESERVATION = "/lyche/reservation/" +SULTEN_ABOUT = "/lyche/about/" +SULTEN_CONTACT = "lyche/contact/" +USER_CHANGE_PASSWORD = "/control-panetl/password/" +ADMIN = "/control-panel/" +ADMIN_USERS = "/control-panel/users/" +ADMIN_ROLES = "/control-panel/roles/" +ADMIN_ROLES_VIEW = "/control-panel/roles//" +ADMIN_ROLES_EDIT = "/control-panel/roles//edit/" +ADMIN_ROLES_CREATE = "/control-panel/roles/create/" +ADMIN_GANGS = "/control-panel/gangs/" +ADMIN_GANGS_CREATE = "/control-panel/gangs/create/" +ADMIN_GANGS_EDIT = "/control-panel/gangs/edit//" +ADMIN_EVENTS = "/control-panel/events/" +ADMIN_EVENTS_EDIT = "/control-panel/events/edit//" +ADMIN_EVENTS_CREATE = "/control-panel/events/create/" +ADMIN_INFORMATION = "/control-panel/information/" +ADMIN_INFORMATION_EDIT = "/control-panel/information/edit//" +ADMIN_INFORMATION_CREATE = "/control-panel/information/create/" +ADMIN_OPENING_HOURS = "/control-panel/opening-hours/" +ADMIN_CLOSED = "/control-panel/closed/" +ADMIN_CLOSED_CREATE = "/control-panel/closed/create/" +ADMIN_CLOSED_EDIT = "/control-panel/closed/edit//" +ADMIN_IMAGES = "/control-panel/images/" +ADMIN_IMAGES_CREATE = "/control-panel/images/create/" +ADMIN_SAKSDOKUMENTER = "/control-panel/saksdokument/" +ADMIN_SAKSDOKUMENTER_CREATE = "/control-panel/saksdokument/create/" +ADMIN_SAKSDOKUMENTER_EDIT = "/control-panel/saksdokument/edit//" +ADMIN_RECRUITMENT = "/control-panel/recruitment/" +ADMIN_RECRUITMENT_EDIT = "/control-panel/recruitment/edit/" +ADMIN_RECRUITMENT_CREATE = "/control-panel/recruitment/create/" +ADMIN_RECRUITMENT_USERS_THREE_INTERVIEW_CRITERIA = "/control-panel/recruitment//users-without-three-interviews/" +ADMIN_RECRUITMENT_USERS_WITHOUT_INTERVIEW = "/control-panel/recruitment//users-without-applications/" +ADMIN_RECRUITMENT_OPEN_TO_OTHER_POSITIONS = "/control-panel/recruitment//users-open-to-other-positions/" +ADMIN_RECRUITMENT_OVERVIEW = "/control-panel/recruitment//recruitment-overview/" +ADMIN_RECRUITMENT_GANG_OVERVIEW = "/control-panel/recruitment//gang-overview/" +ADMIN_RECRUITMENT_GANG_OVERVIEW_REJECTION_EMAIL = "/control-panel/recruitment//gang-overview/rejection-email/" +ADMIN_RECRUITMENT_GANG_POSITION_OVERVIEW = "/control-panel/recruitment//gang/" +ADMIN_RECRUITMENT_GANG_POSITION_CREATE = "/control-panel/recruitment//gang//create/" +ADMIN_RECRUITMENT_GANG_POSITION_EDIT = "/control-panel/recruitment//gang//edit/" +ADMIN_RECRUITMENT_GANG_SEPARATEPOSITION_CREATE = "/control-panel/recruitment//separateposition/create" +ADMIN_RECRUITMENT_GANG_SEPARATEPOSITION_EDIT = "/control-panel/recruitment//separateposition/edit/" +ADMIN_RECRUITMENT_RECRUITER_DASHBOARD = "/control-panel/recruitment//recruiter/dashboard/" +ADMIN_RECRUITMENT_ROOM_OVERVIEW = "/control-panel/recruitment//room-overview/" +ADMIN_RECRUITMENT_ROOM_CREATE = "/control-panel/recruitment//room/create/" +ADMIN_RECRUITMENT_ROOM_EDIT = "/control-panel/recruitment//room/edit//" +ADMIN_RECRUITMENT_GANG_POSITION_APPLICANTS_OVERVIEW = "/control-panel/recruitment//gang//position/" +ADMIN_RECRUITMENT_GANG_POSITION_APPLICANTS_INTERVIEW_NOTES = "/control-panel/recruitment//gang//position//interview-notes/" +ADMIN_RECRUITMENT_GANG_ALL_APPLICATIONS = "/control-panel/recruitment///all-applications/" +ADMIN_RECRUITMENT_GANG_USERS_WITHOUT_INTERVIEW = "/control-panel/recruitment///users-without-interviews/" +ADMIN_RECRUITMENT_SHOW_UNPROCESSED_APPLICANTS = "/control-panel/recruitment//unprocessed-applicants/" +ADMIN_RECRUITMENT_INTERVIEW_AVAILABILITY = "/control-panel/recruitment//interview-availability/" +ADMIN_SULTEN_MENU = "/control-panel/lyche/menu" +ADMIN_SULTEN_MENUITEM_CREATE = "/control-panel/lyche/menuitems/create" +ADMIN_SULTEN_MENUITEM_EDIT = "/control-panel/lyche/menuitems/edit/" +ADMIN_SULTEN_RESERVATIONS = "/control-panel/lyche/reservations" +ADMIN_RECRUITMENT_APPLICANT = "/control-panel/recruitment/view-applicant//" +ADMIN_RECRUITMENT_ALL_POSITIONS = "/control-panel/recruitment//all-positions/" +API_TESTING = "/api-testing/" +COMPONENTS = "/components/" +ROUTE_OVERVIEW = "/route/overview/" +NOT_FOUND = "/not-found" diff --git a/backend/samfundet/routing/metadata.py b/backend/samfundet/routing/metadata.py new file mode 100644 index 000000000..fddfd7e19 --- /dev/null +++ b/backend/samfundet/routing/metadata.py @@ -0,0 +1,59 @@ +import re +from dataclasses import dataclass, field +from typing import Optional, List +from django.utils.html import escape + + +@dataclass +class MetadataItem: + attr: str + key: str + value: str + + +@dataclass +class Metadata: + title: Optional[str] = None + description: Optional[str] = None + canonical_url: Optional[str] = None + items: List[MetadataItem] = field(default_factory=list) + + +def build_metadata_html(metadata: Metadata) -> str: + tags = [] + + if metadata.title: + metadata.items.append(MetadataItem("property", "og:title", metadata.title)) + metadata.items.append(MetadataItem("name", "twitter:title", metadata.title)) + + if metadata.description: + tags.append(f'') + metadata.items.append(MetadataItem("property", "og:description", metadata.description)) + + if metadata.canonical_url: + tags.append(f'') + + for item in metadata.items: + tags.append(f'') + + return "\n".join(tags) + + +def inject_metadata(html: str, metadata: Metadata) -> str: + if metadata.title: + title = escape(metadata.title) + if re.search(r".*?", html, flags=re.IGNORECASE | re.DOTALL): + html = re.sub( + r".*?", + f"{title}", + html, + flags=re.IGNORECASE | re.DOTALL + ) + else: + html = html.replace("", f"{title}") + + meta_html = build_metadata_html(metadata) + + html = html.replace("", f"{meta_html}\n") + + return html diff --git a/backend/samfundet/routing/views.py b/backend/samfundet/routing/views.py new file mode 100644 index 000000000..741dad069 --- /dev/null +++ b/backend/samfundet/routing/views.py @@ -0,0 +1,54 @@ +import os + +from django.conf import settings +from django.http import HttpResponse +from rest_framework.generics import get_object_or_404 + +from samfundet.models import Event +from .metadata import Metadata, MetadataItem, inject_metadata + + +def get_frontend_html() -> str: + path = os.path.join(settings.REACT_BUILD_DIR, 'index.html') + with open(path, 'r') as f: + return f.read() + + +def react_view(request, **kwargs): + """ + Serve the built React index.html + All route parameters from URL are captured in kwargs but not used + (React Router will handle routing on the client side) + """ + try: + return HttpResponse(get_frontend_html()) + except FileNotFoundError: + return HttpResponse("React build not found.", status=500) + + +def is_bot(request): + return True + bot_patterns = [ + 'googlebot', 'bingbot', 'slurp', 'duckduckbot', + 'baiduspider', 'yandexbot', 'facebookexternalhit', + 'twitterbot', 'linkedinbot', 'whatsapp', 'telegrambot', + 'claudebot', 'perplexitybot', 'chatgpt', 'pinterestbot', + ] + user_agent = request.META['HTTP_USER_AGENT'].lower() + return any(pattern in user_agent for pattern in bot_patterns) + + +def react_event_view(request, **kwargs): + if is_bot(request): + event = get_object_or_404(Event, id=kwargs["id"]) + + title = f"{event.title_nb} - Samfundet" + description = event.description_short_nb + + metadata = Metadata(title=title, description=description) + metadata.items.append(MetadataItem("name", "og:custom", "Lorem ipsum")) + + html = get_frontend_html() + return HttpResponse(inject_metadata(html, metadata)) + + return react_view(request, **kwargs) diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index 7151964bf..ab1ec682f 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -1,11 +1,13 @@ # imports from __future__ import annotations +from django.conf.urls.static import static from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from rest_framework import routers from django.urls import path, include +from django.conf import settings import samfundet.view.user_views import samfundet.view.event_views @@ -14,7 +16,9 @@ from samfundet.view import billig_views from . import views +from .routing.views import react_view, react_event_view from .view import recruitment_views +from .routing import frontend_routes # End: imports ----------------------------------------------------------------- router = routers.DefaultRouter() @@ -68,7 +72,11 @@ app_name = 'samfundet' -urlpatterns = [ +def frontend_path(route_path, view, name): + return path(route_path.lstrip('/'), view, name=name) + + +urlpatterns = ([ path('api/', include(router.urls)), path('schema/', SpectacularAPIView.as_view(), name='schema'), path('schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='samfundet:schema'), name='swagger_ui'), @@ -185,4 +193,12 @@ path('recruitment//gang//stats/', views.GangApplicationCountView.as_view(), name='gang-application-stats'), path('recruitment//positions-by-tags/', views.PositionByTagsView.as_view(), name='recruitment_positions_by_tags'), path('recruitment/all-applications/', views.RecruitmentAllApplicationsPerRecruitmentView.as_view(), name='recruitment-all-applications'), -] + + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +] + [ + # Serve React frontend + frontend_path(frontend_routes.EVENT, react_event_view, name='reactapp_event'), + + path("", react_view, name="reactapp"), + path("", react_view),# +]) diff --git a/frontend/build_to_backend.sh b/frontend/build_to_backend.sh new file mode 100755 index 000000000..d42a4dce9 --- /dev/null +++ b/frontend/build_to_backend.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -eu + +yarn build --mode development +rm -rf ../backend/reactapp +mv dist ../backend/reactapp + +echo "Done!" diff --git a/frontend/index.html b/frontend/index.html index 50f126739..2ad8db8e7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + Samfundet diff --git a/frontend/package.json b/frontend/package.json index bb3e36ae4..9549032b9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,7 +36,8 @@ "stylelint:check": "stylelint --config .stylelintrc src/**/*.{css,scss}", "tsc:check": "tsc", "tsc:watch": "tsc --watch", - "verify": "yarn biome:check && yarn tsc:check && yarn stylelint:check" + "verify": "yarn biome:check && yarn tsc:check && yarn stylelint:check", + "export-routes": "tsx scripts/export-routes.ts" }, "dependencies": { "@babel/core": "^7.23.2", @@ -103,6 +104,7 @@ "stylelint-config-standard": "^34.0.0", "stylelint-config-standard-scss": "^11.0.0", "stylelint-scss": "^5.2.1", + "tsx": "^4.20.6", "typescript": "^5.2.2", "vite": "^4.5.14" }, diff --git a/frontend/scripts/export-routes.ts b/frontend/scripts/export-routes.ts new file mode 100644 index 000000000..c460d9562 --- /dev/null +++ b/frontend/scripts/export-routes.ts @@ -0,0 +1,40 @@ +const fs = require('fs'); +const path = require('path'); +const routes = require('../src/routes/frontend.ts'); + +function main() { + const ROUTES_FRONTEND = routes.ROUTES_FRONTEND; + let buffer = `""" +THIS FILE IS AUTOGENERATED! +DO NOT WRITE IN THIS FILE, AS IT WILL BE OVERWRITTEN ON NEXT UPDATE. + +Frontend routes meant for consumption by the Django backend. + +Run 'yarn export-routes' in the frontend directory to regenerate +""" + +`; + + for (const [name, reactPath] of Object.entries(ROUTES_FRONTEND)) { + const varName = name.toUpperCase(); + const djangoPath = (reactPath as string).replace(/:(\w+)/g, '<$1>'); // e.g. /events/:id/ -> /events// + buffer += `${varName} = "${djangoPath}"\n`; + } + + const outputPath = path.join(__dirname, '../../backend/samfundet/routing/frontend_routes.py'); + const dir = path.dirname(outputPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(outputPath, buffer); + + console.log(`Generated Python routes file with ${Object.keys(ROUTES_FRONTEND).length} routes to ${outputPath}`); +} + +try { + main(); +} catch (error) { + console.error('Failed to generate Python routes file:\n', error); + process.exit(1); +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 617942dac..293d6fd6f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -465,6 +465,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/aix-ppc64@npm:0.25.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -479,6 +486,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm64@npm:0.25.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -493,6 +507,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm@npm:0.25.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -507,6 +528,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-x64@npm:0.25.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -521,6 +549,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-arm64@npm:0.25.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -535,6 +570,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-x64@npm:0.25.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -549,6 +591,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-arm64@npm:0.25.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -563,6 +612,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-x64@npm:0.25.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -577,6 +633,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm64@npm:0.25.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -591,6 +654,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm@npm:0.25.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -605,6 +675,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ia32@npm:0.25.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -619,6 +696,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-loong64@npm:0.25.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -633,6 +717,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-mips64el@npm:0.25.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -647,6 +738,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ppc64@npm:0.25.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -661,6 +759,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-riscv64@npm:0.25.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -675,6 +780,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-s390x@npm:0.25.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -689,6 +801,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-x64@npm:0.25.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-arm64@npm:0.25.1" @@ -696,6 +815,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-arm64@npm:0.25.12" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -710,6 +836,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-x64@npm:0.25.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-arm64@npm:0.25.1" @@ -717,6 +850,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-arm64@npm:0.25.12" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -731,6 +871,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-x64@npm:0.25.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openharmony-arm64@npm:0.25.12" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -745,6 +899,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/sunos-x64@npm:0.25.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -759,6 +920,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-arm64@npm:0.25.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -773,6 +941,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-ia32@npm:0.25.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -787,6 +962,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-x64@npm:0.25.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@hookform/resolvers@npm:^4.1.3": version: 4.1.3 resolution: "@hookform/resolvers@npm:4.1.3" @@ -3906,6 +4088,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.25.0": + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + languageName: node + linkType: hard + "escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -4277,6 +4548,7 @@ __metadata: stylelint-config-standard: "npm:^34.0.0" stylelint-config-standard-scss: "npm:^11.0.0" stylelint-scss: "npm:^5.2.1" + tsx: "npm:^4.20.6" typescript: "npm:^5.2.2" vite: "npm:^4.5.14" vite-plugin-svgr: "npm:^4.1.0" @@ -4314,7 +4586,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2": +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -4324,7 +4596,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -4398,6 +4670,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.13.0 + resolution: "get-tsconfig@npm:4.13.0" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/2c49ef8d3907047a107f229fd610386fe3b7fe9e42dfd6b42e7406499493cdda8c62e83e57e8d7a98125610774b9f604d3a0ff308d7f9de5c7ac6d1b07cb6036 + languageName: node + linkType: hard + "getos@npm:^3.2.1": version: 3.2.1 resolution: "getos@npm:3.2.1" @@ -7057,6 +7338,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + "resolve@npm:^1.22.1, resolve@npm:^1.22.8": version: 1.22.10 resolution: "resolve@npm:1.22.10" @@ -8012,6 +8300,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.20.6": + version: 4.20.6 + resolution: "tsx@npm:4.20.6" + dependencies: + esbuild: "npm:~0.25.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/07757a9bf62c271e0a00869b2008c5f2d6e648766536e4faf27d9d8027b7cde1ac8e4871f4bb570c99388bcee0018e6869dad98c07df809b8052f9c549cd216f + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0"