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"