diff --git a/.circleci/config.yml b/.circleci/config.yml
index 96d353d..fbe07a6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -149,13 +149,15 @@ jobs:
password: $DOCKER_HUB_PASSWORD
working_directory: ~/fun
steps:
- - checkout
+ - checkout:
+ path: ~/fun
- restore_cache:
keys:
- v1-dependencies-{{ .Revision }}
- run:
name: Install development dependencies
command: pip install --user .[dev]
+ working_directory: src/app
- save_cache:
paths:
- ~/.local
@@ -167,18 +169,19 @@ jobs:
auth:
username: $DOCKER_HUB_USER
password: $DOCKER_HUB_PASSWORD
- working_directory: ~/fun
+ working_directory: ~/fun/src/app
steps:
- - checkout
+ - checkout:
+ path: ~/fun
- restore_cache:
keys:
- v1-dependencies-{{ .Revision }}
- run:
name: Lint code with black
- command: ~/.local/bin/black src/mork tests --check
+ command: ~/.local/bin/black mork --check
- run:
name: Lint code with Ruff check
- command: ~/.local/bin/ruff check .
+ command: ~/.local/bin/ruff check mork
test:
docker:
@@ -188,6 +191,7 @@ jobs:
password: $DOCKER_HUB_PASSWORD
environment:
MORK_DB_HOST: localhost
+ PYTHONPATH: /home/circleci/fun
- image: cimg/postgres:12.14
auth:
username: $DOCKER_HUB_USER
@@ -196,12 +200,16 @@ jobs:
POSTGRES_DB: mork-db
POSTGRES_USER: fun
POSTGRES_PASSWORD: pass
- working_directory: ~/fun
+ working_directory: ~/fun/src/app
steps:
- - checkout
+ - checkout:
+ path: ~/fun
+ - attach_workspace:
+ at: ~/fun
- restore_cache:
keys:
- v1-dependencies-{{ .Revision }}
+ # Create test database
- run: sudo apt-get update
- run: sudo apt-get install postgresql-client
- run: whoami
@@ -213,6 +221,37 @@ jobs:
name: Run tests
command: ~/.local/bin/pytest
+ # ---- Email jobs ----
+ build-mails:
+ docker:
+ - image: cimg/node:20.16.0
+ auth:
+ username: $DOCKER_HUB_USER
+ password: $DOCKER_HUB_PASSWORD
+ working_directory: ~/fun/src/mail
+ steps:
+ - checkout:
+ path: ~/fun
+ - restore_cache:
+ keys:
+ - v1-mail-dependencies-{{ .Revision }}
+ # If the yarn.lock file is not up-to-date with the package.json file,
+ # using the --frozen-lockfile should fail.
+ - run:
+ name: Install node dependencies
+ command: yarn install --frozen-lockfile
+ - run:
+ name: Build mails
+ command: yarn build
+ - persist_to_workspace:
+ root: ~/fun
+ paths:
+ - src/app/mork/templates
+ - save_cache:
+ paths:
+ - ./node_modules
+ key: v1-mail-dependencies-{{ .Revision }}
+
# ---- Packaging jobs ----
package:
docker:
@@ -220,9 +259,10 @@ jobs:
auth:
username: $DOCKER_HUB_USER
password: $DOCKER_HUB_PASSWORD
- working_directory: ~/fun
+ working_directory: ~/fun/src/app
steps:
- - checkout
+ - checkout:
+ path: ~/fun
- restore_cache:
keys:
- v1-dependencies-{{ .Revision }}
@@ -235,7 +275,7 @@ jobs:
- persist_to_workspace:
root: ~/fun
paths:
- - dist
+ - src/app/dist
# Store packages as artifacts to download/test them
- store_artifacts:
path: ~/fun/dist
@@ -402,6 +442,11 @@ workflows:
filters:
tags:
only: /.*/
+ # Build mails
+ - build-mails:
+ filters:
+ tags:
+ only: /.*/
# Docker jobs
#
@@ -428,6 +473,7 @@ workflows:
- test:
requires:
- build
+ - build-mails
filters:
tags:
only: /.*/
diff --git a/.env.dist b/.env.dist
index 39ad1a2..d5b31d9 100644
--- a/.env.dist
+++ b/.env.dist
@@ -29,6 +29,20 @@ MORK_CELERY_RESULT_BACKEND=redis://redis:6379/0
MORK_CELERY_BROKER_TRANSPORT_OPTIONS={}
MORK_CELERY_TASK_DEFAULT_QUEUE=celery
+# Emails
+MORK_EMAIL_HOST=mailcatcher
+MORK_EMAIL_HOST_USER=
+MORK_EMAIL_HOST_PASSWORD=
+MORK_EMAIL_PORT=1025
+MORK_EMAIL_USE_TLS=False
+MORK_EMAIL_FROM=from@fun-mooc.fr
+MORK_EMAIL_RATE_LIMIT=100/m
+MORK_EMAIL_MAX_RETRIES=3
+MORK_EMAIL_SITE_NAME="France Université Numérique"
+MORK_EMAIL_SITE_BASE_URL=https://fun-mooc.fr
+MORK_EMAIL_SITE_LOGIN_URL=https://lms.fun-mooc.fr/login
+
+
# Python
PYTHONPATH=/app
@@ -48,13 +62,3 @@ MYSQL_PASSWORD=password
POSTGRES_DB=mork-db
POSTGRES_USER=fun
POSTGRES_PASSWORD=pass
-
-# Emails
-EMAIL_HOST=mailcatcher
-EMAIL_HOST_USER=
-EMAIL_HOST_PASSWORD=
-EMAIL_PORT=1025
-EMAIL_USE_TLS=False
-EMAIL_FROM=from@fun-mooc.fr
-EMAIL_RATE_LIMIT=100/m
-EMAIL_MAX_RETRIES=3
diff --git a/.gitignore b/.gitignore
index f0d9208..7ed39d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,12 @@ ENV/
env.bak/
venv.bak/
+# npm
+node_modules
+
+# Mails
+src/app/mork/templates/
+
# Logs
*.log
diff --git a/Dockerfile b/Dockerfile
index e98689e..e098c14 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,21 +9,35 @@ RUN apt-get update && \
apt-get -y upgrade && \
rm -rf /var/lib/apt/lists/*
-
# -- Builder --
FROM base AS builder
WORKDIR /build
-COPY . /build/
+# Copy required python dependencies
+COPY ./src/app /build
+
+RUN mkdir /install && \
+ pip install --prefix=/install .
+
+# ---- mails ----
+FROM node:20 AS mail-builder
-RUN pip install .
+COPY ./src/mail /mail/app
+WORKDIR /mail/app
+RUN yarn install --frozen-lockfile && \
+ yarn build
+
# -- Core --
FROM base AS core
-COPY --from=builder /usr/local /usr/local
+# Copy installed python dependencies
+COPY --from=builder /install /usr/local
+
+# Copy mork application (see .dockerignore)
+COPY ./src/app /app/
WORKDIR /app
@@ -31,9 +45,8 @@ WORKDIR /app
# -- Development --
FROM core AS development
-# Copy all sources, not only runtime-required files
-COPY . /app/
-
+# Switch to privileged user to uninstall app
+USER root:root
# Uninstall mork and re-install it in editable mode along with development
# dependencies
@@ -52,6 +65,9 @@ FROM core AS production
ARG DOCKER_USER=1000
USER ${DOCKER_USER}
+# Copy mork mails
+COPY --from=mail-builder /mail/app/mork/templates /app/src/app/mork/templates
+
CMD ["uvicorn", \
"mork.api:app", \
"--proxy-headers", \
diff --git a/Makefile b/Makefile
index 88f2d0a..b2e4d10 100644
--- a/Makefile
+++ b/Makefile
@@ -2,10 +2,11 @@
SHELL := /bin/bash
# -- Docker
-COMPOSE = bin/compose
-COMPOSE_EXEC = $(COMPOSE) exec
-COMPOSE_RUN = $(COMPOSE) run --rm --no-deps
-COMPOSE_RUN_API = $(COMPOSE_RUN) api
+COMPOSE = bin/compose
+COMPOSE_EXEC = $(COMPOSE) exec
+COMPOSE_RUN = $(COMPOSE) run --rm --no-deps
+COMPOSE_RUN_API = $(COMPOSE_RUN) api
+COMPOSE_RUN_MAIL = $(COMPOSE_RUN) mail-generator
# -- MySQL
EDX_DB_HOST = mysql
@@ -25,6 +26,8 @@ MORK_TEST_DB_NAME ?= test-mork-db
# -- Celery
MORK_CELERY_SERVER_PORT ?= 5555
+# -- Mail
+MAIL_YARN = $(COMPOSE_RUN_MAIL) yarn
# ==============================================================================
# RULES
@@ -48,7 +51,10 @@ bootstrap: \
.env \
build \
run \
- seed-edx-database
+ migrate \
+ seed-edx-database \
+ mails-install \
+ mails-build
.PHONY: bootstrap
build: ## build the app containers
@@ -147,17 +153,17 @@ lint: \
lint-black: ## lint python sources with black
@echo 'lint:black started…'
- @$(COMPOSE_RUN_API) black --config pyproject.toml src/mork tests
+ @$(COMPOSE_RUN_API) black mork
.PHONY: lint-black
lint-ruff: ## lint python sources with ruff
@echo 'lint:ruff started…'
- @$(COMPOSE_RUN_API) ruff check --config pyproject.toml .
+ @$(COMPOSE_RUN_API) ruff check .
.PHONY: lint-ruff
lint-ruff-fix: ## lint and fix python sources with ruff
@echo 'lint:ruff-fix started…'
- @$(COMPOSE_RUN_API) ruff check --config pyproject.toml . --fix
+ @$(COMPOSE_RUN_API) ruff check . --fix
.PHONY: lint-ruff-fix
## -- Tests
@@ -170,6 +176,25 @@ test: \
.PHONY: test
+# -- Mail generator
+
+mails-build: ## Convert mjml files to html and text
+ @$(MAIL_YARN) build
+.PHONY: mails-build
+
+mails-build-html-to-plain-text: ## Convert html files to text
+ @$(MAIL_YARN) build-html-to-plain-text
+.PHONY: mails-build-html-to-plain-text
+
+mails-build-mjml-to-html: ## Convert mjml files to html and text
+ @$(MAIL_YARN) build-mjml-to-html
+.PHONY: mails-build-mjml-to-html
+
+mails-install: ## mail-generator yarn install
+ @$(MAIL_YARN) install
+.PHONY: mails-install
+
+
# -- Misc
help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/bin/alembic b/bin/alembic
index 920bac6..24fc4df 100755
--- a/bin/alembic
+++ b/bin/alembic
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
-bin/compose run --rm api alembic -c src/mork/alembic.ini "$@"
+bin/compose run --rm api alembic -c /app/mork/alembic.ini "$@"
diff --git a/docker-compose.yml b/docker-compose.yml
index 8b44764..e0239c3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -33,7 +33,7 @@ services:
- "${MORK_SERVER_PORT:-8100}"
- "--reload"
volumes:
- - .:/app
+ - ./src/app:/app
celery:
image: mork:development
@@ -43,7 +43,7 @@ services:
ports:
- "5555:5555"
volumes:
- - ./src:/app
+ - ./src/app:/app
- ./bin/seed_edx_database.py:/opt/src/seed_edx_database.py
depends_on:
- api
@@ -60,6 +60,15 @@ services:
ports:
- "1081:1080"
+ mail-generator:
+ image: node:20
+ user: "${DOCKER_USER:-1000}"
+ environment:
+ HOME: /tmp
+ volumes:
+ - ".:/app"
+ working_dir: /app/src/mail
+
mysql:
image: mysql:5.7
ports:
diff --git a/logging-config.dev.yaml b/src/app/logging-config.dev.yaml
similarity index 100%
rename from logging-config.dev.yaml
rename to src/app/logging-config.dev.yaml
diff --git a/logging-config.prod.yaml b/src/app/logging-config.prod.yaml
similarity index 100%
rename from logging-config.prod.yaml
rename to src/app/logging-config.prod.yaml
diff --git a/src/mork/__init__.py b/src/app/mork/__init__.py
similarity index 100%
rename from src/mork/__init__.py
rename to src/app/mork/__init__.py
diff --git a/src/mork/alembic.ini b/src/app/mork/alembic.ini
similarity index 100%
rename from src/mork/alembic.ini
rename to src/app/mork/alembic.ini
diff --git a/src/mork/api/__init__.py b/src/app/mork/api/__init__.py
similarity index 100%
rename from src/mork/api/__init__.py
rename to src/app/mork/api/__init__.py
diff --git a/src/mork/api/auth.py b/src/app/mork/api/auth.py
similarity index 100%
rename from src/mork/api/auth.py
rename to src/app/mork/api/auth.py
diff --git a/src/mork/api/models.py b/src/app/mork/api/models.py
similarity index 100%
rename from src/mork/api/models.py
rename to src/app/mork/api/models.py
diff --git a/src/mork/api/routes/health.py b/src/app/mork/api/routes/health.py
similarity index 100%
rename from src/mork/api/routes/health.py
rename to src/app/mork/api/routes/health.py
diff --git a/src/mork/api/routes/tasks.py b/src/app/mork/api/routes/tasks.py
similarity index 100%
rename from src/mork/api/routes/tasks.py
rename to src/app/mork/api/routes/tasks.py
diff --git a/src/mork/celery/__init__.py b/src/app/mork/celery/__init__.py
similarity index 100%
rename from src/mork/celery/__init__.py
rename to src/app/mork/celery/__init__.py
diff --git a/src/mork/celery/celery_app.py b/src/app/mork/celery/celery_app.py
similarity index 100%
rename from src/mork/celery/celery_app.py
rename to src/app/mork/celery/celery_app.py
diff --git a/src/mork/celery/tasks.py b/src/app/mork/celery/tasks.py
similarity index 54%
rename from src/mork/celery/tasks.py
rename to src/app/mork/celery/tasks.py
index 34e85b0..6537cec 100644
--- a/src/mork/celery/tasks.py
+++ b/src/app/mork/celery/tasks.py
@@ -1,11 +1,7 @@
"""Mork Celery tasks."""
-import smtplib
from datetime import datetime
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
from logging import getLogger
-from smtplib import SMTPException
from sqlalchemy import select
@@ -13,6 +9,7 @@
from mork.conf import settings
from mork.database import MorkDB
from mork.exceptions import EmailAlreadySent, EmailSendError
+from mork.mail import send_email
from mork.models import EmailStatus
logger = getLogger(__name__)
@@ -60,48 +57,6 @@ def check_email_already_sent(email_adress: str):
return result
-def send_email(email_address: str, username: str):
- """Initialize connection to SMTP and send a warning email."""
- html = f"""\
-
-
- Hello {username},
- Your account will be closed soon! If you want to keep it, please log in!
-
-
- """
-
- # Create a multipart message and set headers
- message = MIMEMultipart()
- message["From"] = settings.EMAIL_FROM
- message["To"] = email_address
- message["Subject"] = "Your account will be closed soon"
-
- # Attach the HTML part
- message.attach(MIMEText(html, "html"))
-
- # Send the email
- with smtplib.SMTP(
- host=settings.EMAIL_HOST, port=settings.EMAIL_PORT
- ) as smtp_server:
- if settings.EMAIL_USE_TLS:
- smtp_server.starttls()
- if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
- smtp_server.login(
- user=settings.EMAIL_HOST_USER,
- password=settings.EMAIL_HOST_PASSWORD,
- )
- try:
- smtp_server.sendmail(
- from_addr=settings.EMAIL_FROM,
- to_addrs=email_address,
- msg=message.as_string(),
- )
- except SMTPException as exc:
- logger.error(f"Sending email failed: {exc} ")
- raise EmailSendError("Failed sending an email") from exc
-
-
def mark_email_status(email_address: str):
"""Mark the email status in database."""
db = MorkDB()
diff --git a/src/mork/conf.py b/src/app/mork/conf.py
similarity index 94%
rename from src/mork/conf.py
rename to src/app/mork/conf.py
index fc83417..7c7b423 100644
--- a/src/mork/conf.py
+++ b/src/app/mork/conf.py
@@ -33,6 +33,9 @@ class Settings(BaseSettings):
# Alembic
ALEMBIC_CFG_PATH: Path = ROOT_PATH / "alembic.ini"
+ # Static path
+ STATIC_PATH: Path = ROOT_PATH / "static"
+
# Mork database
DB_ENGINE: str = "postgresql+psycopg"
DB_HOST: str = "postgresql"
@@ -58,9 +61,12 @@ class Settings(BaseSettings):
EMAIL_HOST_PASSWORD: str = ""
EMAIL_PORT: int = 1025
EMAIL_USE_TLS: bool = False
- EMAIL_FROM: str = "from@fun-mooc.fr"
+ EMAIL_FROM: str = ""
EMAIL_RATE_LIMIT: str = "100/m"
EMAIL_MAX_RETRIES: int = 3
+ EMAIL_SITE_NAME: str = ""
+ EMAIL_SITE_BASE_URL: str = ""
+ EMAIL_SITE_LOGIN_URL: str = ""
# Celery
broker_url: str = Field("redis://redis:6379/0", alias="MORK_CELERY_BROKER_URL")
diff --git a/src/mork/database.py b/src/app/mork/database.py
similarity index 100%
rename from src/mork/database.py
rename to src/app/mork/database.py
diff --git a/src/mork/edx/database.py b/src/app/mork/edx/database.py
similarity index 100%
rename from src/mork/edx/database.py
rename to src/app/mork/edx/database.py
diff --git a/src/mork/edx/factories.py b/src/app/mork/edx/factories.py
similarity index 100%
rename from src/mork/edx/factories.py
rename to src/app/mork/edx/factories.py
diff --git a/src/mork/edx/mixins.py b/src/app/mork/edx/mixins.py
similarity index 100%
rename from src/mork/edx/mixins.py
rename to src/app/mork/edx/mixins.py
diff --git a/src/mork/edx/models.py b/src/app/mork/edx/models.py
similarity index 100%
rename from src/mork/edx/models.py
rename to src/app/mork/edx/models.py
diff --git a/src/mork/exceptions.py b/src/app/mork/exceptions.py
similarity index 100%
rename from src/mork/exceptions.py
rename to src/app/mork/exceptions.py
diff --git a/src/mork/factories.py b/src/app/mork/factories.py
similarity index 100%
rename from src/mork/factories.py
rename to src/app/mork/factories.py
diff --git a/src/app/mork/mail.py b/src/app/mork/mail.py
new file mode 100644
index 0000000..ea0b90d
--- /dev/null
+++ b/src/app/mork/mail.py
@@ -0,0 +1,86 @@
+"""Email related functions."""
+
+import smtplib
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from logging import getLogger
+from smtplib import SMTPException
+
+from jinja2 import Environment, FileSystemLoader
+
+from mork.conf import settings
+from mork.exceptions import EmailSendError
+from mork.templatetags.extra_tags import SVGStaticTag
+
+logger = getLogger(__name__)
+
+
+def render_template(template: str, context) -> str:
+ """Render a Jinja template into HTML."""
+ template_env = Environment(
+ loader=FileSystemLoader(
+ [
+ settings.ROOT_PATH / "templates/html",
+ settings.ROOT_PATH / "templates/text",
+ ]
+ ),
+ autoescape=True,
+ extensions=[SVGStaticTag],
+ )
+ template = template_env.get_template(template)
+ return template.render(**context)
+
+
+def send_email(email_address: str, username: str):
+ """Initialize connection to SMTP and send a warning email."""
+ template_vars = {
+ "title": "Votre compte va être supprimé dans 30 jours.",
+ "email": email_address,
+ "fullname": username,
+ "site": {
+ "name": settings.EMAIL_SITE_NAME,
+ "url": settings.EMAIL_SITE_BASE_URL,
+ "login_url": settings.EMAIL_SITE_LOGIN_URL,
+ },
+ }
+ html = render_template(
+ "warning_email.html",
+ template_vars,
+ )
+
+ text = render_template(
+ "warning_email.txt",
+ template_vars,
+ )
+
+ # Create a multipart message (with MIME type multipart/alternative) and set headers
+ message = MIMEMultipart("alternative")
+ message["From"] = settings.EMAIL_FROM
+ message["To"] = email_address
+ message["Subject"] = "Votre compte va bientôt être supprimé"
+
+ # Attach the HTML parts. According to RFC 2046, the last part of a multipart
+ # message, in this case the HTML message, is best and preferred
+ message.attach(MIMEText(text, "plain"))
+ message.attach(MIMEText(html, "html"))
+
+ # Send the email
+ with smtplib.SMTP(
+ host=settings.EMAIL_HOST, port=settings.EMAIL_PORT
+ ) as smtp_server:
+ if settings.EMAIL_USE_TLS:
+ smtp_server.starttls()
+ if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
+ smtp_server.login(
+ user=settings.EMAIL_HOST_USER,
+ password=settings.EMAIL_HOST_PASSWORD,
+ )
+ try:
+ smtp_server.sendmail(
+ from_addr=settings.EMAIL_FROM,
+ to_addrs=email_address,
+ msg=message.as_string(),
+ )
+ except SMTPException as exc:
+ logger.error(f"Sending email failed: {exc} ")
+ raise EmailSendError("Failed sending an email") from exc
diff --git a/src/mork/migrations/__init__.py b/src/app/mork/migrations/__init__.py
similarity index 100%
rename from src/mork/migrations/__init__.py
rename to src/app/mork/migrations/__init__.py
diff --git a/src/mork/migrations/env.py b/src/app/mork/migrations/env.py
similarity index 100%
rename from src/mork/migrations/env.py
rename to src/app/mork/migrations/env.py
diff --git a/src/mork/migrations/script.py.mako b/src/app/mork/migrations/script.py.mako
similarity index 100%
rename from src/mork/migrations/script.py.mako
rename to src/app/mork/migrations/script.py.mako
diff --git a/src/mork/migrations/versions/608d075c6e99_initialize_database.py b/src/app/mork/migrations/versions/608d075c6e99_initialize_database.py
similarity index 100%
rename from src/mork/migrations/versions/608d075c6e99_initialize_database.py
rename to src/app/mork/migrations/versions/608d075c6e99_initialize_database.py
diff --git a/src/mork/models.py b/src/app/mork/models.py
similarity index 100%
rename from src/mork/models.py
rename to src/app/mork/models.py
diff --git a/src/app/mork/static/images/logo-fr.svg b/src/app/mork/static/images/logo-fr.svg
new file mode 100644
index 0000000..81b920c
--- /dev/null
+++ b/src/app/mork/static/images/logo-fr.svg
@@ -0,0 +1,188 @@
+
+
+
diff --git a/src/app/mork/templatetags/__init__.py b/src/app/mork/templatetags/__init__.py
new file mode 100644
index 0000000..b017503
--- /dev/null
+++ b/src/app/mork/templatetags/__init__.py
@@ -0,0 +1 @@
+"""Template tags module."""
diff --git a/src/app/mork/templatetags/extra_tags.py b/src/app/mork/templatetags/extra_tags.py
new file mode 100644
index 0000000..4d9decd
--- /dev/null
+++ b/src/app/mork/templatetags/extra_tags.py
@@ -0,0 +1,19 @@
+"""Custom template tags for Mork."""
+
+from jinja2_simple_tags import StandaloneTag
+
+from mork.conf import settings
+from mork.utils import svg_to_datauri
+
+
+class SVGStaticTag(StandaloneTag):
+ """Extension Jinja tag for converting SVG files to data URI."""
+
+ tags = {"svg_static"}
+
+ def render(self, path: str):
+ """Return a SVG static file into data URI format."""
+ full_path = settings.STATIC_PATH / path
+ if full_path.exists():
+ return svg_to_datauri(full_path)
+ return ""
diff --git a/tests/__init__.py b/src/app/mork/tests/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to src/app/mork/tests/__init__.py
diff --git a/tests/api/__init__.py b/src/app/mork/tests/api/__init__.py
similarity index 100%
rename from tests/api/__init__.py
rename to src/app/mork/tests/api/__init__.py
diff --git a/tests/api/test_auth.py b/src/app/mork/tests/api/test_auth.py
similarity index 100%
rename from tests/api/test_auth.py
rename to src/app/mork/tests/api/test_auth.py
diff --git a/tests/api/test_health.py b/src/app/mork/tests/api/test_health.py
similarity index 100%
rename from tests/api/test_health.py
rename to src/app/mork/tests/api/test_health.py
diff --git a/tests/api/test_tasks.py b/src/app/mork/tests/api/test_tasks.py
similarity index 100%
rename from tests/api/test_tasks.py
rename to src/app/mork/tests/api/test_tasks.py
diff --git a/src/app/mork/tests/celery/__init__.py b/src/app/mork/tests/celery/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/celery/test_tasks.py b/src/app/mork/tests/celery/test_tasks.py
similarity index 50%
rename from tests/celery/test_tasks.py
rename to src/app/mork/tests/celery/test_tasks.py
index 456ddc5..80fdff6 100644
--- a/tests/celery/test_tasks.py
+++ b/src/app/mork/tests/celery/test_tasks.py
@@ -1,12 +1,6 @@
"""Tests for Mork Celery tasks."""
-import smtplib
-from unittest.mock import MagicMock
-
-import pytest
-
-from mork.celery.tasks import check_email_already_sent, mark_email_status, send_email
-from mork.exceptions import EmailSendError
+from mork.celery.tasks import check_email_already_sent, mark_email_status
from mork.factories import EmailStatusFactory
@@ -27,36 +21,6 @@ class MockMorkDB:
assert check_email_already_sent(email_address)
-def test_send_email(monkeypatch):
- """Test the `send_email` function."""
-
- mock_SMTP = MagicMock()
- monkeypatch.setattr("mork.celery.tasks.smtplib.SMTP", mock_SMTP)
-
- test_address = "john.doe@example.com"
- test_username = "JohnDoe"
- send_email(email_address=test_address, username=test_username)
-
- assert mock_SMTP.return_value.__enter__.return_value.sendmail.call_count == 1
-
-
-def test_send_email_with_smtp_exception(monkeypatch):
- """Test the `send_email` function with an SMTP exception."""
-
- mock_SMTP = MagicMock()
- mock_SMTP.return_value.__enter__.return_value.sendmail.side_effect = (
- smtplib.SMTPException
- )
-
- monkeypatch.setattr("mork.celery.tasks.smtplib.SMTP", mock_SMTP)
-
- test_address = "john.doe@example.com"
- test_username = "JohnDoe"
-
- with pytest.raises(EmailSendError, match="Failed sending an email"):
- send_email(email_address=test_address, username=test_username)
-
-
def test_mark_email_status(monkeypatch, db_session):
"""Test the `mark_email_status` function."""
diff --git a/tests/conftest.py b/src/app/mork/tests/conftest.py
similarity index 74%
rename from tests/conftest.py
rename to src/app/mork/tests/conftest.py
index a469911..587a0ae 100644
--- a/tests/conftest.py
+++ b/src/app/mork/tests/conftest.py
@@ -2,7 +2,11 @@
# ruff: noqa: F401
+from pathlib import Path
+
from .fixtures.app import http_client
from .fixtures.asynchronous import anyio_backend
from .fixtures.auth import auth_headers
from .fixtures.db import db_engine, db_session, edx_db
+
+TEST_STATIC_PATH = Path(__file__).parent / "static"
diff --git a/src/app/mork/tests/edx/__init__.py b/src/app/mork/tests/edx/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/edx/test_mixins.py b/src/app/mork/tests/edx/test_mixins.py
similarity index 100%
rename from tests/edx/test_mixins.py
rename to src/app/mork/tests/edx/test_mixins.py
diff --git a/tests/edx/test_models.py b/src/app/mork/tests/edx/test_models.py
similarity index 100%
rename from tests/edx/test_models.py
rename to src/app/mork/tests/edx/test_models.py
diff --git a/tests/fixtures/__init__.py b/src/app/mork/tests/fixtures/__init__.py
similarity index 100%
rename from tests/fixtures/__init__.py
rename to src/app/mork/tests/fixtures/__init__.py
diff --git a/tests/fixtures/app.py b/src/app/mork/tests/fixtures/app.py
similarity index 100%
rename from tests/fixtures/app.py
rename to src/app/mork/tests/fixtures/app.py
diff --git a/tests/fixtures/asynchronous.py b/src/app/mork/tests/fixtures/asynchronous.py
similarity index 100%
rename from tests/fixtures/asynchronous.py
rename to src/app/mork/tests/fixtures/asynchronous.py
diff --git a/tests/fixtures/auth.py b/src/app/mork/tests/fixtures/auth.py
similarity index 100%
rename from tests/fixtures/auth.py
rename to src/app/mork/tests/fixtures/auth.py
diff --git a/tests/fixtures/db.py b/src/app/mork/tests/fixtures/db.py
similarity index 100%
rename from tests/fixtures/db.py
rename to src/app/mork/tests/fixtures/db.py
diff --git a/src/app/mork/tests/static/images/red-square.svg b/src/app/mork/tests/static/images/red-square.svg
new file mode 100644
index 0000000..d1ca47e
--- /dev/null
+++ b/src/app/mork/tests/static/images/red-square.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/app/mork/tests/templatetags/__init__.py b/src/app/mork/tests/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/mork/tests/templatetags/test_extra_tags.py b/src/app/mork/tests/templatetags/test_extra_tags.py
new file mode 100644
index 0000000..f66c39b
--- /dev/null
+++ b/src/app/mork/tests/templatetags/test_extra_tags.py
@@ -0,0 +1,28 @@
+"""Tests for Mork extra Jinja tags."""
+
+from mork.templatetags.extra_tags import SVGStaticTag
+from mork.tests.conftest import TEST_STATIC_PATH
+
+
+def test_svgstatictag_render(monkeypatch):
+ """Test the SVGStaticTag `render` method returns file encoded in base64."""
+ static_filepath = TEST_STATIC_PATH / "images/red-square.svg"
+
+ red_square_base64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxIDEiPgo8cGF0aCBkPSJNMCwwaDF2MUgwIiBmaWxsPSIjZjAwIi8+Cjwvc3ZnPg==" # noqa: E501
+
+ monkeypatch.setattr(
+ "mork.templatetags.extra_tags.SVGStaticTag.__init__", lambda x: None
+ )
+
+ assert SVGStaticTag().render(static_filepath) == red_square_base64
+
+
+def test_base64tag_render_unknown_file(monkeypatch):
+ """Test that the SVGStaticTag `render` method should return an empty string."""
+ static_filepath = "unknown-static-file.txt"
+
+ monkeypatch.setattr(
+ "mork.templatetags.extra_tags.SVGStaticTag.__init__", lambda x: None
+ )
+
+ assert SVGStaticTag().render(static_filepath) == ""
diff --git a/src/app/mork/tests/test_mail.py b/src/app/mork/tests/test_mail.py
new file mode 100644
index 0000000..e872b25
--- /dev/null
+++ b/src/app/mork/tests/test_mail.py
@@ -0,0 +1,70 @@
+"""Tests for Mork mail functions."""
+
+import smtplib
+from unittest.mock import MagicMock
+
+import pytest
+
+from mork.exceptions import EmailSendError
+from mork.mail import render_template, send_email
+
+
+def test_render_template():
+ """Test the `render_template` function."""
+ template_vars = {
+ "title": "Votre compte va être supprimé dans 30 jours.",
+ "email": "test@example.com",
+ "fullname": "John Doe",
+ "site": {
+ "name": "Example site",
+ "url": "http://base_url.com",
+ "login_url": "http://url.com/login",
+ },
+ }
+ render_html = render_template("warning_email.html", template_vars)
+ assert template_vars["title"] in render_html
+ assert template_vars["email"] in render_html
+ assert template_vars["fullname"] in render_html
+ assert template_vars["site"]["name"] in render_html
+ assert template_vars["site"]["url"] in render_html
+ assert template_vars["site"]["login_url"] in render_html
+ assert 'src="data:' in render_html
+
+ render_text = render_template("warning_email.txt", template_vars)
+ assert template_vars["title"] in render_text
+ assert template_vars["email"] in render_text
+ assert template_vars["fullname"] in render_text
+ assert template_vars["site"]["name"] in render_text
+ assert template_vars["site"]["url"] in render_text
+ assert template_vars["site"]["login_url"] in render_text
+ assert "data:" in render_text
+
+
+def test_send_email(monkeypatch):
+ """Test the `send_email` function."""
+
+ mock_SMTP = MagicMock()
+ monkeypatch.setattr("mork.mail.smtplib.SMTP", mock_SMTP)
+
+ test_address = "john.doe@example.com"
+ test_username = "JohnDoe"
+ send_email(email_address=test_address, username=test_username)
+
+ assert mock_SMTP.return_value.__enter__.return_value.sendmail.call_count == 1
+
+
+def test_send_email_with_smtp_exception(monkeypatch):
+ """Test the `send_email` function with an SMTP exception."""
+
+ mock_SMTP = MagicMock()
+ mock_SMTP.return_value.__enter__.return_value.sendmail.side_effect = (
+ smtplib.SMTPException
+ )
+
+ monkeypatch.setattr("mork.mail.smtplib.SMTP", mock_SMTP)
+
+ test_address = "john.doe@example.com"
+ test_username = "JohnDoe"
+
+ with pytest.raises(EmailSendError, match="Failed sending an email"):
+ send_email(email_address=test_address, username=test_username)
diff --git a/tests/test_models.py b/src/app/mork/tests/test_models.py
similarity index 100%
rename from tests/test_models.py
rename to src/app/mork/tests/test_models.py
diff --git a/src/app/mork/tests/test_utils.py b/src/app/mork/tests/test_utils.py
new file mode 100644
index 0000000..3408d59
--- /dev/null
+++ b/src/app/mork/tests/test_utils.py
@@ -0,0 +1,14 @@
+"""Tests for utility functions."""
+
+from mork.tests.conftest import TEST_STATIC_PATH
+from mork.utils import svg_to_datauri
+
+
+def test_utils_svg_to_datauri_path():
+ """Image to base64 from path."""
+
+ red_square_base64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxIDEiPgo8cGF0aCBkPSJNMCwwaDF2MUgwIiBmaWxsPSIjZjAwIi8+Cjwvc3ZnPg==" # noqa: E501
+
+ assert (
+ svg_to_datauri(TEST_STATIC_PATH / "images/red-square.svg") == red_square_base64
+ )
diff --git a/src/app/mork/utils.py b/src/app/mork/utils.py
new file mode 100644
index 0000000..54b6276
--- /dev/null
+++ b/src/app/mork/utils.py
@@ -0,0 +1,10 @@
+"""Utility functions."""
+
+from pathlib import Path
+
+from datauri import DataURI
+
+
+def svg_to_datauri(path: Path | str):
+ """Return the data URI string of an SVG image."""
+ return str(DataURI.from_file(path))
diff --git a/pyproject.toml b/src/app/pyproject.toml
similarity index 96%
rename from pyproject.toml
rename to src/app/pyproject.toml
index bb2d937..5998ada 100644
--- a/pyproject.toml
+++ b/src/app/pyproject.toml
@@ -26,8 +26,11 @@ keywords = ["FastAPI", "Celery", "emails", "Open edX"]
dependencies = [
"alembic==1.13.2",
"celery[redis]==5.4.0",
+ "Jinja2==3.1.4",
+ "jinja2-simple-tags==0.6.1",
"psycopg[binary]==3.2.1",
"pydantic_settings==2.4.0",
+ "python-datauri==2.2.0",
"fastapi[standard]==0.112.1",
"pymysql==1.1.1",
"sentry-sdk==2.13.0",
@@ -109,7 +112,7 @@ section-order = ["future", "standard-library", "third-party", "mork", "first-par
mork = ["mork"]
[tool.ruff.lint.per-file-ignores]
-"tests/*" = [
+"mork/**/tests/*" = [
"ARG", # flake8-unused-arguments
"D", # pydocstyle
"S", # flake8-bandit
diff --git a/src/mail/bin/html-to-plain-text b/src/mail/bin/html-to-plain-text
new file mode 100755
index 0000000..00dd9e6
--- /dev/null
+++ b/src/mail/bin/html-to-plain-text
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+set -eo pipefail
+# Run html-to-text to convert all html files to text files
+DIR_MAILS="../app/mork/templates/"
+
+if [ ! -d "${DIR_MAILS}" ]; then
+ mkdir -p "${DIR_MAILS}";
+fi
+
+if [ ! -d "${DIR_MAILS}"html/ ]; then
+ mkdir -p "${DIR_MAILS}"html/;
+ exit;
+fi
+
+for file in "${DIR_MAILS}"html/*.html;
+ do html-to-text -j ./html-to-text.config.json < "$file" > "${file%.html}".txt; done;
+
+if [ ! -d "${DIR_MAILS}"text/ ]; then
+ mkdir -p "${DIR_MAILS}"text/;
+fi
+
+mv "${DIR_MAILS}"html/*.txt "${DIR_MAILS}"text/;
diff --git a/src/mail/bin/mjml-to-html b/src/mail/bin/mjml-to-html
new file mode 100755
index 0000000..89e6b54
--- /dev/null
+++ b/src/mail/bin/mjml-to-html
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+# Run mjml command to convert all mjml templates to html files
+DIR_MAILS="../app/mork/templates/html/"
+
+if [ ! -d "${DIR_MAILS}" ]; then
+ mkdir -p "${DIR_MAILS}";
+fi
+mjml mjml/*.mjml -o "${DIR_MAILS}";
diff --git a/src/mail/html-to-text.config.json b/src/mail/html-to-text.config.json
new file mode 100644
index 0000000..8d2e57a
--- /dev/null
+++ b/src/mail/html-to-text.config.json
@@ -0,0 +1,11 @@
+{
+ "wordwrap": 600,
+ "selectors": [
+ {
+ "selector": "h1",
+ "options": {
+ "uppercase": false
+ }
+ }
+ ]
+}
diff --git a/src/mail/mjml/partial/footer.mjml b/src/mail/mjml/partial/footer.mjml
new file mode 100644
index 0000000..a962c7b
--- /dev/null
+++ b/src/mail/mjml/partial/footer.mjml
@@ -0,0 +1,7 @@
+
+
+
+ Cet email a été envoyé à {{ email }} par {{ site.name }}
+
+
+
diff --git a/src/mail/mjml/partial/header.mjml b/src/mail/mjml/partial/header.mjml
new file mode 100644
index 0000000..0c4c7b5
--- /dev/null
+++ b/src/mail/mjml/partial/header.mjml
@@ -0,0 +1,43 @@
+
+ {{ title }}
+
+ {{ title }}
+
+
+
+
+
+
+
+
+ /* Reset */
+ h1, h2, h3, h4, h5, h6, p {
+ margin: 0;
+ padding: 0;
+ }
+
+ a {
+ color: inherit;
+ }
+
+
+ /* Global styles */
+ h1 {
+ color: #055FD2;
+ font-size: 2rem;
+ line-height: 1em;
+ font-weight: 700;
+ }
+
+ .wrapper {
+ background: #FFFFFF;
+ border-radius: 0 0 6px 6px;
+ box-shadow: 0 0 6px rgba(2 117 180 / 0.3);
+ }
+
+
diff --git a/src/mail/mjml/warning_email.mjml b/src/mail/mjml/warning_email.mjml
new file mode 100644
index 0000000..8d5bb7e
--- /dev/null
+++ b/src/mail/mjml/warning_email.mjml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Votre compte va être supprimé dans 30 jours.
+
+
+
+
+
+
+ {{ fullname }},
+ Vous ne vous êtes pas connecté sur fun-mooc.fr depuis longtemps.
+
+ Malheureusement, sans action de votre part et
+ conformément à notre politique de protection des données, nous procéderons
+ à la suppression de votre compte dans 30 jours.
+
+
+ Si vous souhaitez conserver votre compte, veuillez vous connecter à la plateforme.
+
+
+
+
+
+
+ Se connecter
+
+
+
+
+
+
+
diff --git a/src/mail/package.json b/src/mail/package.json
new file mode 100644
index 0000000..59a7b04
--- /dev/null
+++ b/src/mail/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "mail_mjml",
+ "version": "0.0.1",
+ "description": "An util to generate html and text jinja templates from mjml templates",
+ "type": "module",
+ "dependencies": {
+ "@html-to/text-cli": "0.5.4",
+ "mjml": "4.15.3"
+ },
+ "private": true,
+ "scripts": {
+ "build-mjml-to-html": "./bin/mjml-to-html",
+ "build-html-to-plain-text": "./bin/html-to-plain-text",
+ "build": "yarn build-mjml-to-html; yarn build-html-to-plain-text;"
+ },
+ "volta": {
+ "node": "20.16.0"
+ },
+ "repository": "https://github.com/openfun/mork",
+ "author": "France Université Numérique",
+ "license": "MIT"
+}
diff --git a/src/mail/yarn.lock b/src/mail/yarn.lock
new file mode 100644
index 0000000..0a9311b
--- /dev/null
+++ b/src/mail/yarn.lock
@@ -0,0 +1,1211 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/runtime@^7.23.9":
+ version "7.25.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb"
+ integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@html-to/text-cli@0.5.4":
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/@html-to/text-cli/-/text-cli-0.5.4.tgz#f40804d139e0eaa43c923c3d845cbf1f8b133acf"
+ integrity sha512-V7WDfiYjXcibHGD6q61oW8HD68UPvBVkKit0X+9v54nTmLe8KDCc+56STleqqP7CzuEK5f/1jqa652fnr9Pmsw==
+ dependencies:
+ "@selderee/plugin-htmlparser2" "^0.11.0"
+ aspargvs "^0.6.0"
+ deepmerge "^4.3.1"
+ htmlparser2 "^8.0.2"
+ selderee "^0.11.0"
+
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ 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"
+
+"@one-ini/wasm@0.1.1":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323"
+ integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==
+
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
+"@selderee/plugin-htmlparser2@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517"
+ integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==
+ dependencies:
+ domhandler "^5.0.3"
+ selderee "^0.11.0"
+
+abbrev@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
+ integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
+
+ansi-colors@^4.1.1:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
+ integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
+ integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+
+ansi-styles@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+aspargvs@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aspargvs/-/aspargvs-0.6.0.tgz#15991c35425b044cb99868b6b3cfa7e051a28424"
+ integrity sha512-yUrWCd1hkK5UtDOne1gM3O+FoTFGQ+BVlSd4G7FczBz8+JaFn1uzvQzROxwp9hmlhIUtwSwyRuV9mHgd/WbXxg==
+ dependencies:
+ peberminta "^0.8.0"
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+binary-extensions@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
+ integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+
+boolbase@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+ integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@~3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+camel-case@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
+ integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==
+ dependencies:
+ no-case "^2.2.0"
+ upper-case "^1.1.1"
+
+cheerio-select@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
+ integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
+ dependencies:
+ boolbase "^1.0.0"
+ css-select "^5.1.0"
+ css-what "^6.1.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+
+cheerio@1.0.0-rc.12, cheerio@^1.0.0-rc.12:
+ version "1.0.0-rc.12"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
+ integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ htmlparser2 "^8.0.1"
+ parse5 "^7.0.0"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+
+chokidar@^3.0.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
+ integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+clean-css@^4.2.1:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
+ integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==
+ dependencies:
+ source-map "~0.6.0"
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+commander@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+ integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+commander@^2.19.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
+ integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
+
+config-chain@^1.1.13:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
+ integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
+cross-spawn@^7.0.0:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+css-select@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+ integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^6.1.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
+ nth-check "^2.0.1"
+
+css-what@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
+ integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+
+deepmerge@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
+ integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
+detect-node@2.1.0, detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+dom-serializer@^1.0.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
+ integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.2.0"
+ entities "^2.0.0"
+
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+ integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
+ integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
+ dependencies:
+ domelementtype "^2.0.1"
+
+domhandler@^4.2.0:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
+ integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+ dependencies:
+ domelementtype "^2.2.0"
+
+domhandler@^5.0.2, domhandler@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+ dependencies:
+ domelementtype "^2.3.0"
+
+domutils@^2.4.2:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+ integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+ dependencies:
+ dom-serializer "^1.0.1"
+ domelementtype "^2.2.0"
+ domhandler "^4.2.0"
+
+domutils@^3.0.1, domutils@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+ integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+ dependencies:
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+editorconfig@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3"
+ integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==
+ dependencies:
+ "@one-ini/wasm" "0.1.1"
+ commander "^10.0.0"
+ minimatch "9.0.1"
+ semver "^7.5.3"
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
+entities@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+ integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
+entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+escalade@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
+ integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
+
+escape-goat@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-3.0.0.tgz#e8b5fb658553fe8a3c4959c316c6ebb8c842b19c"
+ integrity sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+foreground-child@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
+ integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
+fsevents@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob@^10.3.10, glob@^10.3.3:
+ version "10.4.5"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
+ integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
+he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+html-minifier@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56"
+ integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==
+ dependencies:
+ camel-case "^3.0.0"
+ clean-css "^4.2.1"
+ commander "^2.19.0"
+ he "^1.2.0"
+ param-case "^2.1.1"
+ relateurl "^0.2.7"
+ uglify-js "^3.5.1"
+
+htmlparser2@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7"
+ integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^3.3.0"
+ domutils "^2.4.2"
+ entities "^2.0.0"
+
+htmlparser2@^8.0.1, htmlparser2@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
+ integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ entities "^4.4.0"
+
+htmlparser2@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23"
+ integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.1.0"
+ entities "^4.5.0"
+
+ini@^1.3.4:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+jackspeak@^3.1.2:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
+ integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
+js-beautify@^1.6.14:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64"
+ integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==
+ dependencies:
+ config-chain "^1.1.13"
+ editorconfig "^1.0.4"
+ glob "^10.3.3"
+ js-cookie "^3.0.5"
+ nopt "^7.2.0"
+
+js-cookie@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
+ integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
+
+juice@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/juice/-/juice-10.0.0.tgz#c6b717ded8be4b969f12503ac9cfbd2604d35937"
+ integrity sha512-9f68xmhGrnIi6DBkiiP3rUrQN33SEuaKu1+njX6VgMP+jwZAsnT33WIzlrWICL9matkhYu3OyrqSUP55YTIdGg==
+ dependencies:
+ cheerio "^1.0.0-rc.12"
+ commander "^6.1.0"
+ mensch "^0.3.4"
+ slick "^1.12.2"
+ web-resource-inliner "^6.0.1"
+
+leac@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
+ integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
+
+lodash@^4.17.15, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lower-case@^1.1.1:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
+ integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==
+
+lru-cache@^10.2.0:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
+mensch@^0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/mensch/-/mensch-0.3.4.tgz#770f91b46cb16ea5b204ee735768c3f0c491fecd"
+ integrity sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==
+
+mime@^2.4.6:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+minimatch@9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
+ integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^9.0.3, minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
+mjml-accordion@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-accordion/-/mjml-accordion-4.15.3.tgz#10e4c4297df3ad8dfa709fc64e887f89bfbff0a8"
+ integrity sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-body@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-body/-/mjml-body-4.15.3.tgz#8885b2921f6daa1a287e8aea0924ee1fc4aaf222"
+ integrity sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-button@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-button/-/mjml-button-4.15.3.tgz#34baf2d7fbf77a5febe6993e311103723279adbd"
+ integrity sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-carousel@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-carousel/-/mjml-carousel-4.15.3.tgz#fe82d2c4c8020ef14f3b360316c670f7da294193"
+ integrity sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-cli@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-cli/-/mjml-cli-4.15.3.tgz#5638f1919c952d224f51970a2fbf3141dee6d487"
+ integrity sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ chokidar "^3.0.0"
+ glob "^10.3.10"
+ html-minifier "^4.0.0"
+ js-beautify "^1.6.14"
+ lodash "^4.17.21"
+ minimatch "^9.0.3"
+ mjml-core "4.15.3"
+ mjml-migrate "4.15.3"
+ mjml-parser-xml "4.15.3"
+ mjml-validator "4.15.3"
+ yargs "^17.7.2"
+
+mjml-column@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-column/-/mjml-column-4.15.3.tgz#ffc538f6b87a7340697f88600330110a40f82c05"
+ integrity sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-core@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-core/-/mjml-core-4.15.3.tgz#96c30f49340b95bb9c825a6479557cc9ad1af6c6"
+ integrity sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ cheerio "1.0.0-rc.12"
+ detect-node "^2.0.4"
+ html-minifier "^4.0.0"
+ js-beautify "^1.6.14"
+ juice "^10.0.0"
+ lodash "^4.17.21"
+ mjml-migrate "4.15.3"
+ mjml-parser-xml "4.15.3"
+ mjml-validator "4.15.3"
+
+mjml-divider@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-divider/-/mjml-divider-4.15.3.tgz#2aadaf7e9955a9d9473f7093598f933aa289c683"
+ integrity sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-group@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-group/-/mjml-group-4.15.3.tgz#7e4418d7d4b5d5d5e4d6af9865c25d6d358a7f75"
+ integrity sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-attributes@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz#4c81e561982fca2657bf3dda7576fcafec778b66"
+ integrity sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-breakpoint@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz#be1fbe6b4f6cd77f7f666b2cb9e48e81f727b74f"
+ integrity sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-font@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-font/-/mjml-head-font-4.15.3.tgz#0340872d0ffe9e29044d66ede452575cb7da3ddf"
+ integrity sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-html-attributes@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz#852710724b976fac7aabd648f5f9770bfa1e21e5"
+ integrity sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-preview@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz#710ce159974bf2924edb7f920dd05280a433afd3"
+ integrity sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-style@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-style/-/mjml-head-style-4.15.3.tgz#66a9a3926888681578c2550c7444e4f8cbddfda3"
+ integrity sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head-title@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head-title/-/mjml-head-title-4.15.3.tgz#ccbd11a7771965f5ac5f3069f6c4f74668c9e6ea"
+ integrity sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-head@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-head/-/mjml-head-4.15.3.tgz#3e7311af0de4911dd167c877cf04d4291206cd2f"
+ integrity sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-hero@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-hero/-/mjml-hero-4.15.3.tgz#c51d9f6d1f37acf7e35d827ce3116f8a4aaf9037"
+ integrity sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-image@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-image/-/mjml-image-4.15.3.tgz#e652a4b18663c7d93cc22d88eed45f3fdb9c82ea"
+ integrity sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-migrate@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-migrate/-/mjml-migrate-4.15.3.tgz#65e2b335a2ffc7e29e09f96793961d0e8f081d98"
+ integrity sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ js-beautify "^1.6.14"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+ mjml-parser-xml "4.15.3"
+ yargs "^17.7.2"
+
+mjml-navbar@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-navbar/-/mjml-navbar-4.15.3.tgz#c9805a98f24a475dd3feece58e690838c075fdff"
+ integrity sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-parser-xml@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz#8b94550dbe0d16155ea6cd1fb34bc53dba6f59ed"
+ integrity sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ detect-node "2.1.0"
+ htmlparser2 "^9.1.0"
+ lodash "^4.17.15"
+
+mjml-preset-core@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz#d4972292b7db42b51d08feb1104ad23ee5d3b87f"
+ integrity sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ mjml-accordion "4.15.3"
+ mjml-body "4.15.3"
+ mjml-button "4.15.3"
+ mjml-carousel "4.15.3"
+ mjml-column "4.15.3"
+ mjml-divider "4.15.3"
+ mjml-group "4.15.3"
+ mjml-head "4.15.3"
+ mjml-head-attributes "4.15.3"
+ mjml-head-breakpoint "4.15.3"
+ mjml-head-font "4.15.3"
+ mjml-head-html-attributes "4.15.3"
+ mjml-head-preview "4.15.3"
+ mjml-head-style "4.15.3"
+ mjml-head-title "4.15.3"
+ mjml-hero "4.15.3"
+ mjml-image "4.15.3"
+ mjml-navbar "4.15.3"
+ mjml-raw "4.15.3"
+ mjml-section "4.15.3"
+ mjml-social "4.15.3"
+ mjml-spacer "4.15.3"
+ mjml-table "4.15.3"
+ mjml-text "4.15.3"
+ mjml-wrapper "4.15.3"
+
+mjml-raw@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-raw/-/mjml-raw-4.15.3.tgz#ab771a3d9b5b05583ff90653bf7ca74ec96ffc20"
+ integrity sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-section@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-section/-/mjml-section-4.15.3.tgz#ba2b524449b18a4fbbdf05c223a0627e02afa7a9"
+ integrity sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-social@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-social/-/mjml-social-4.15.3.tgz#8d1ac1dfd3c56077e1106ead283a40878a2c32d9"
+ integrity sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-spacer@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-spacer/-/mjml-spacer-4.15.3.tgz#9a2a4b9d51df2e9cae9fbe9848fd722ef0dfd335"
+ integrity sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-table@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-table/-/mjml-table-4.15.3.tgz#702271761e450172bd5dda9ffcb2faefed3f5db0"
+ integrity sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-text@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-text/-/mjml-text-4.15.3.tgz#045ca711b0c18d2ba163c5a9f296a0c7ed82dbfc"
+ integrity sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+
+mjml-validator@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-validator/-/mjml-validator-4.15.3.tgz#c7934ca66ff41fa7293927b1328cfbafa8268ffb"
+ integrity sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+
+mjml-wrapper@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz#6526824608514561376ecfdab079275f53cc8706"
+ integrity sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ lodash "^4.17.21"
+ mjml-core "4.15.3"
+ mjml-section "4.15.3"
+
+mjml@4.15.3:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/mjml/-/mjml-4.15.3.tgz#d46996d63e957ae946b2da6ca78fcef5186beee9"
+ integrity sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==
+ dependencies:
+ "@babel/runtime" "^7.23.9"
+ mjml-cli "4.15.3"
+ mjml-core "4.15.3"
+ mjml-migrate "4.15.3"
+ mjml-preset-core "4.15.3"
+ mjml-validator "4.15.3"
+
+no-case@^2.2.0:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
+ integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
+ dependencies:
+ lower-case "^1.1.1"
+
+node-fetch@^2.6.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+nopt@^7.2.0:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7"
+ integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
+ dependencies:
+ abbrev "^2.0.0"
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+nth-check@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
+ integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+ dependencies:
+ boolbase "^1.0.0"
+
+package-json-from-dist@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
+ integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
+
+param-case@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
+ integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==
+ dependencies:
+ no-case "^2.2.0"
+
+parse5-htmlparser2-tree-adapter@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
+ integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
+ dependencies:
+ domhandler "^5.0.2"
+ parse5 "^7.0.0"
+
+parse5@^7.0.0:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
+ integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
+ dependencies:
+ entities "^4.4.0"
+
+parseley@^0.12.0:
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef"
+ integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==
+ dependencies:
+ leac "^0.6.0"
+ peberminta "^0.9.0"
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-scurry@^1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
+ integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+ dependencies:
+ lru-cache "^10.2.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
+peberminta@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.8.0.tgz#acf7b105f3d13c8ac28cad81f2f5fe4698507590"
+ integrity sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw==
+
+peberminta@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352"
+ integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+ integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
+relateurl@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
+ integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+selderee@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a"
+ integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==
+ dependencies:
+ parseley "^0.12.0"
+
+semver@^7.5.3:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+signal-exit@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
+slick@^1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7"
+ integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==
+
+source-map@~0.6.0:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^5.0.1, string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+uglify-js@^3.5.1:
+ version "3.19.1"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.1.tgz#2d5df6a0872c43da43187968308d7741d44b8056"
+ integrity sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==
+
+upper-case@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
+ integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==
+
+valid-data-url@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/valid-data-url/-/valid-data-url-3.0.1.tgz#826c1744e71b5632e847dd15dbd45b9fb38aa34f"
+ integrity sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==
+
+web-resource-inliner@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz#df0822f0a12028805fe80719ed52ab6526886e02"
+ integrity sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==
+ dependencies:
+ ansi-colors "^4.1.1"
+ escape-goat "^3.0.0"
+ htmlparser2 "^5.0.0"
+ mime "^2.4.6"
+ node-fetch "^2.6.0"
+ valid-data-url "^3.0.0"
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.7.2:
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"