diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..2a77325ef --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Owned by the Platform team +* @uktrade/platform diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a4c4e2cf0..1fcd1d028 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,5 @@ Addresses [DBTP-](https://uktrade.atlassian.net/browse/DBTP-) -Please add any relevant context for you pull request here, or delete this if none needed. - --- ## Checklist: @@ -15,4 +13,4 @@ Please add any relevant context for you pull request here, or delete this if non - [ ] Includes any applicable changes to the documentation in this code base - [ ] Includes link(s) to any applicable changes to the documentation in the [DBT Platform Documentation](https://platform.readme.trade.gov.uk/) (can be to a pull request) ### Tasks: -- [ ] [Trigger the pull request regression tests for this branch](https://github.com/uktrade/platform-tools?tab=readme-ov-file#regression-tests) and confirm that they are passing +- [ ] [Run the end to end tests for this branch]([https://github.com/uktrade/platform-tools?tab=readme-ov-file#regression-tests](https://github.com/uktrade/platform-end-to-end-tests?tab=readme-ov-file#running-the-tests)) and confirm that they are passing diff --git a/.gitignore b/.gitignore index 2c1ba122d..410a95036 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ tests/platform_helper/test-application-deploy/copilot dist +.platform-helper-config.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34fa5c158..6740d1175 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -54,6 +54,6 @@ repos: - id: trufflehog name: TruffleHog description: Detect secrets in your data. - entry: bash -c 'trufflehog git file://. --since-commit HEAD --no-verification --fail --exclude-paths=.trufflehogignore' + entry: bash -c 'trufflehog filesystem . --no-verification --fail --exclude-paths=.trufflehogignore' language: system stages: ["commit", "push"] diff --git a/.trufflehogignore b/.trufflehogignore index a32a6c5fa..16c0fc0cd 100644 --- a/.trufflehogignore +++ b/.trufflehogignore @@ -3,6 +3,3 @@ node_modules poetry.lock venv - -# See https://github.com/trufflesecurity/trufflehog/issues/3602 -tests/platform_helper/utils/test_aws.py diff --git a/README.md b/README.md index 207606e8d..bb68faef4 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ For an optional manual check, install the package locally and test everything wo #### Merging to main -- Merging to `main` will trigger the `pull-request-regression-tests` pipeline in the _platform-tools_ AWS account to run regression tests +- Merging to `main` will trigger the `pull-request-end-to-end-tests` pipeline in the _platform-tools_ AWS account to run regression tests - We use the `release-please` GitHub action to create and update a _release PR_ when changes are merged to `main` - The _release PR_ will automatically update the _pyproject.toml_ version number and generate release notes based on the commits merged since the last release - Merging the _release PR_ will create a draft GitHub release for the next version with release notes @@ -153,7 +153,7 @@ For an optional manual check, install the package locally and test everything wo Publishing a GitHub release should automatically: -- Run the full `pull-request-regression-tests` pipeline +- Run the full `pull-request-end-to-end-tests` pipeline - Trigger a CodeBuild project called `platform-tools-build` in the _platform-tools_ AWS account to run. This runs the _buildspec-pypi.yml_ file which contains the build steps to publish the new `platform-helper` package version to PyPI - Trigger a rebuild of the DBT Platform Documentation, so it includes the latest release documentation (currently WIP) - Push a notification to the development community via the #developers channel in Slack diff --git a/codecov.yml b/codecov.yml index 169ade4bd..d6c3ece5e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,4 +4,5 @@ codecov: after_n_builds: 4 ignore: + - "tests" - "**/tests/**" diff --git a/dbt_platform_helper/COMMANDS.md b/dbt_platform_helper/COMMANDS.md index a4afb8432..f6370d576 100644 --- a/dbt_platform_helper/COMMANDS.md +++ b/dbt_platform_helper/COMMANDS.md @@ -222,7 +222,7 @@ platform-helper codebase build --app --codebase - `--app ` - AWS application name - `--codebase ` - - The codebase name as specified in the pipelines.yml file + - The codebase name as specified in the platform-config.yml file - `--commit ` - GitHub commit hash - `--help ` _Defaults to False._ @@ -232,8 +232,6 @@ platform-helper codebase build --app --codebase [↩ Parent](#platform-helper-codebase) - Trigger a CodePipeline pipeline based deployment. - ## Usage ``` @@ -248,7 +246,7 @@ platform-helper codebase deploy --app --env --codeba - `--env ` - AWS Copilot environment - `--codebase ` - - The codebase name as specified in the pipelines.yml file + - The codebase name as specified in the platform-config.yml file - `--commit ` - GitHub commit hash - `--help ` _Defaults to False._ diff --git a/dbt_platform_helper/commands/codebase.py b/dbt_platform_helper/commands/codebase.py index b712d6592..c84906db3 100644 --- a/dbt_platform_helper/commands/codebase.py +++ b/dbt_platform_helper/commands/codebase.py @@ -1,21 +1,18 @@ import json import os -import stat -import subprocess -from pathlib import Path import click -import requests -import yaml -from boto3 import Session -from dbt_platform_helper.utils.application import Application -from dbt_platform_helper.utils.application import ApplicationNotFoundError -from dbt_platform_helper.utils.application import load_application -from dbt_platform_helper.utils.aws import get_aws_session_or_abort +from dbt_platform_helper.domain.codebase import Codebase +from dbt_platform_helper.exceptions import ApplicationDeploymentNotTriggered +from dbt_platform_helper.exceptions import ApplicationEnvironmentNotFoundError +from dbt_platform_helper.exceptions import ApplicationNotFoundError +from dbt_platform_helper.exceptions import CopilotCodebaseNotFoundError +from dbt_platform_helper.exceptions import ImageNotFoundError +from dbt_platform_helper.exceptions import NoCopilotCodebasesFoundError +from dbt_platform_helper.exceptions import NotInCodeBaseRepositoryError from dbt_platform_helper.utils.click import ClickDocOptGroup -from dbt_platform_helper.utils.files import mkfile -from dbt_platform_helper.utils.template import setup_templates +from dbt_platform_helper.utils.git import CommitNotFoundError from dbt_platform_helper.utils.versioning import ( check_platform_helper_version_needs_update, ) @@ -30,92 +27,15 @@ def codebase(): @codebase.command() def prepare(): """Sets up an application codebase for use within a DBT platform project.""" - templates = setup_templates() - - repository = ( - subprocess.run(["git", "remote", "get-url", "origin"], capture_output=True, text=True) - .stdout.split("/")[-1] - .strip() - .removesuffix(".git") - ) - - if repository.endswith("-deploy") or Path("./copilot").exists(): + try: + Codebase().prepare() + except NotInCodeBaseRepositoryError: + # TODO print error attached to exception click.secho( "You are in the deploy repository; make sure you are in the application codebase repository.", fg="red", ) - exit(1) - - builder_configuration_url = "https://raw.githubusercontent.com/uktrade/ci-image-builder/main/image_builder/configuration/builder_configuration.yml" - builder_configuration_response = requests.get(builder_configuration_url) - builder_configuration_content = yaml.safe_load( - builder_configuration_response.content.decode("utf-8") - ) - builder_versions = next( - ( - item - for item in builder_configuration_content["builders"] - if item["name"] == "paketobuildpacks/builder-jammy-base" - ), - None, - ) - builder_version = max(x["version"] for x in builder_versions["versions"]) - # Temporary hack until https://uktrade.atlassian.net/browse/DBTP-351 is done - # Will need a change in tests/platform_helper/expected_files/.copilot/config.yml, when removed. - builder_version = min(builder_version, "0.4.240") - - Path("./.copilot/phases").mkdir(parents=True, exist_ok=True) - image_build_run_contents = templates.get_template(f".copilot/image_build_run.sh").render() - - config_contents = templates.get_template(f".copilot/config.yml").render( - repository=repository, builder_version=builder_version - ) - - click.echo( - mkfile(Path("."), ".copilot/image_build_run.sh", image_build_run_contents, overwrite=True) - ) - - image_build_run_file = Path(".copilot/image_build_run.sh") - image_build_run_file.chmod(image_build_run_file.stat().st_mode | stat.S_IEXEC) - - click.echo(mkfile(Path("."), ".copilot/config.yml", config_contents, overwrite=True)) - - for phase in ["build", "install", "post_build", "pre_build"]: - phase_contents = templates.get_template(f".copilot/phases/{phase}.sh").render() - - click.echo(mkfile(Path("./.copilot"), f"phases/{phase}.sh", phase_contents, overwrite=True)) - - -def list_latest_images(ecr_client, ecr_repository_name, codebase_repository): - paginator = ecr_client.get_paginator("describe_images") - describe_images_response_iterator = paginator.paginate( - repositoryName=ecr_repository_name, - filter={"tagStatus": "TAGGED"}, - ) - images = [] - for page in describe_images_response_iterator: - images += page["imageDetails"] - - sorted_images = sorted( - images, - key=lambda i: i["imagePushedAt"], - reverse=True, - ) - - MAX_RESULTS = 20 - - for image in sorted_images[:MAX_RESULTS]: - try: - commit_tag = next(t for t in image["imageTags"] if t.startswith("commit-")) - if not commit_tag: - continue - - commit_hash = commit_tag.replace("commit-", "") - click.echo( - f" - https://github.com/{codebase_repository}/commit/{commit_hash} - published: {image['imagePushedAt']}" - ) - except StopIteration: - continue + raise click.Abort @codebase.command() @@ -128,184 +48,98 @@ def list_latest_images(ecr_client, ecr_repository_name, codebase_repository): ) def list(app, with_images): """List available codebases for the application.""" - session = get_aws_session_or_abort() - application = load_application_or_abort(session, app) - ssm_client = session.client("ssm") - ecr_client = session.client("ecr") - parameters = ssm_client.get_parameters_by_path( - Path=f"/copilot/applications/{application.name}/codebases", - Recursive=True, - )["Parameters"] - - codebases = [json.loads(p["Value"]) for p in parameters] - - if not codebases: - click.secho(f'No codebases found for application "{application.name}"', fg="red") + try: + Codebase().list(app, with_images) + except NoCopilotCodebasesFoundError: + click.secho( + f"""No codebases found for application "{app}""", + fg="red", + ) + raise click.Abort + except ApplicationNotFoundError: + click.secho( + f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{app}"; ensure you have set the environment variable "AWS_PROFILE" correctly.""", + fg="red", + ) raise click.Abort - - click.echo("The following codebases are available:") - - for codebase in codebases: - click.echo(f"- {codebase['name']} (https://github.com/{codebase['repository']})") - if with_images: - list_latest_images( - ecr_client, f"{application.name}/{codebase['name']}", codebase["repository"] - ) - - click.echo("") @codebase.command() @click.option("--app", help="AWS application name", required=True) @click.option( - "--codebase", help="The codebase name as specified in the pipelines.yml file", required=True + "--codebase", + help="The codebase name as specified in the platform-config.yml file", + required=True, ) @click.option("--commit", help="GitHub commit hash", required=True) def build(app, codebase, commit): """Trigger a CodePipeline pipeline based build.""" - session = get_aws_session_or_abort() - load_application_or_abort(session, app) - - check_if_commit_exists = subprocess.run( - ["git", "branch", "-r", "--contains", f"{commit}"], capture_output=True, text=True - ) - - if check_if_commit_exists.stderr: + try: + Codebase().build(app, codebase, commit) + except ApplicationNotFoundError: click.secho( - f"""The commit hash "{commit}" either does not exist or you need to run `git fetch`.""", + f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{app}"; ensure you have set the environment variable "AWS_PROFILE" correctly.""", fg="red", ) raise click.Abort - - codebuild_client = session.client("codebuild") - build_url = start_build_with_confirmation( - codebuild_client, - f'You are about to build "{app}" for "{codebase}" with commit "{commit}". Do you want to continue?', - { - "projectName": f"codebuild-{app}-{codebase}", - "artifactsOverride": {"type": "NO_ARTIFACTS"}, - "sourceVersion": commit, - }, - ) - - if build_url: - return click.echo( - "Your build has been triggered. Check your build progress in the AWS Console: " - f"{build_url}", + except CommitNotFoundError: + click.secho( + f'The commit hash "{commit}" either does not exist or you need to run `git fetch`.', + fg="red", ) - - return click.echo("Your build was not triggered.") + raise click.Abort + except ApplicationDeploymentNotTriggered: + click.secho( + f"Your build for {codebase} was not triggered.", + fg="red", + ) + raise click.Abort @codebase.command() @click.option("--app", help="AWS application name", required=True) @click.option("--env", help="AWS Copilot environment", required=True) @click.option( - "--codebase", help="The codebase name as specified in the pipelines.yml file", required=True + "--codebase", + help="The codebase name as specified in the platform-config.yml file", + required=True, ) @click.option("--commit", help="GitHub commit hash", required=True) def deploy(app, env, codebase, commit): - """Trigger a CodePipeline pipeline based deployment.""" - session = get_aws_session_or_abort() - application = load_application_with_environment(session, app, env) - check_codebase_exists(session, application, codebase) - check_image_exists(session, application, codebase, commit) - - codebuild_client = session.client("codebuild") - build_url = start_build_with_confirmation( - codebuild_client, - f'You are about to deploy "{app}" for "{codebase}" with commit "{commit}" to the "{env}" environment. Do you want to continue?', - { - "projectName": f"pipeline-{application.name}-{codebase}-BuildProject", - "artifactsOverride": {"type": "NO_ARTIFACTS"}, - "sourceTypeOverride": "NO_SOURCE", - "environmentVariablesOverride": [ - {"name": "COPILOT_ENVIRONMENT", "value": env}, - {"name": "IMAGE_TAG", "value": f"commit-{commit}"}, - ], - }, - ) - - if build_url: - return click.echo( - "Your deployment has been triggered. Check your build progress in the AWS Console: " - f"{build_url}", - ) - - return click.echo("Your deployment was not triggered.") - - -def load_application_or_abort(session: Session, app: str) -> Application: try: - return load_application(app, default_session=session) + Codebase().deploy(app, env, codebase, commit) except ApplicationNotFoundError: click.secho( f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{app}"; ensure you have set the environment variable "AWS_PROFILE" correctly.""", fg="red", ) raise click.Abort - - -def check_image_exists(session: Session, application: Application, codebase: str, commit: str): - ecr_client = session.client("ecr") - try: - ecr_client.describe_images( - repositoryName=f"{application.name}/{codebase}", - imageIds=[{"imageTag": f"commit-{commit}"}], - ) - except ecr_client.exceptions.RepositoryNotFoundException: + except ApplicationEnvironmentNotFoundError: click.secho( - f'The ECR Repository for codebase "{codebase}" does not exist.', + f"""The environment "{env}" either does not exist or has not been deployed.""", fg="red", ) raise click.Abort - except ecr_client.exceptions.ImageNotFoundException: + # TODO: don't hide json decode error + except ( + CopilotCodebaseNotFoundError, + json.JSONDecodeError, + ): click.secho( - f'The commit hash "{commit}" has not been built into an image, try the ' - "`platform-helper codebase build` command first.", + f"""The codebase "{codebase}" either does not exist or has not been deployed.""", fg="red", ) raise click.Abort - - -def check_codebase_exists(session: Session, application: Application, codebase: str): - ssm_client = session.client("ssm") - try: - parameter = ssm_client.get_parameter( - Name=f"/copilot/applications/{application.name}/codebases/{codebase}" - ) - value = parameter["Parameter"]["Value"] - json.loads(value) - except (KeyError, ValueError, json.JSONDecodeError, ssm_client.exceptions.ParameterNotFound): + except ImageNotFoundError: click.secho( - f"""The codebase "{codebase}" either does not exist or has not been deployed.""", + f'The commit hash "{commit}" has not been built into an image, try the ' + "`platform-helper codebase build` command first.", fg="red", ) raise click.Abort - - -def load_application_with_environment(session: Session, app, env): - application = load_application_or_abort(session, app) - - if not application.environments.get(env): + except ApplicationDeploymentNotTriggered: click.secho( - f"""The environment "{env}" either does not exist or has not been deployed.""", + f"Your deployment for {codebase} was not triggered.", fg="red", ) raise click.Abort - return application - - -def get_build_url_from_arn(build_arn: str) -> str: - _, _, _, region, account_id, project_name, build_id = build_arn.split(":") - project_name = project_name.removeprefix("build/") - return ( - f"https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/{account_id}/projects/" - f"{project_name}/build/{project_name}%3A{build_id}" - ) - - -def start_build_with_confirmation(codebuild_client, confirmation_message, build_options): - if click.confirm(confirmation_message): - response = codebuild_client.start_build(**build_options) - return get_build_url_from_arn(response["build"]["arn"]) diff --git a/dbt_platform_helper/constants.py b/dbt_platform_helper/constants.py index 5373f46ec..d7d1ed649 100644 --- a/dbt_platform_helper/constants.py +++ b/dbt_platform_helper/constants.py @@ -3,3 +3,4 @@ CODEBASE_PIPELINES_KEY = "codebase_pipelines" ENVIRONMENTS_KEY = "environments" DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION = "5" +PLATFORM_HELPER_CACHE_FILE = ".platform-helper-config-cache.yml" diff --git a/dbt_platform_helper/domain/codebase.py b/dbt_platform_helper/domain/codebase.py new file mode 100644 index 000000000..952848b8a --- /dev/null +++ b/dbt_platform_helper/domain/codebase.py @@ -0,0 +1,222 @@ +import json +import stat +import subprocess +from collections.abc import Callable +from pathlib import Path + +import click +import requests +import yaml +from boto3 import Session + +from dbt_platform_helper.exceptions import ApplicationDeploymentNotTriggered +from dbt_platform_helper.exceptions import ApplicationEnvironmentNotFoundError +from dbt_platform_helper.exceptions import NoCopilotCodebasesFoundError +from dbt_platform_helper.exceptions import NotInCodeBaseRepositoryError +from dbt_platform_helper.utils.application import Application +from dbt_platform_helper.utils.application import load_application +from dbt_platform_helper.utils.aws import check_codebase_exists +from dbt_platform_helper.utils.aws import check_image_exists +from dbt_platform_helper.utils.aws import get_aws_session_or_abort +from dbt_platform_helper.utils.aws import get_build_url_from_arn +from dbt_platform_helper.utils.aws import list_latest_images +from dbt_platform_helper.utils.aws import start_build_extraction +from dbt_platform_helper.utils.files import mkfile +from dbt_platform_helper.utils.git import check_if_commit_exists +from dbt_platform_helper.utils.template import setup_templates + + +class Codebase: + def __init__( + self, + input_fn: Callable[[str], str] = click.prompt, + echo_fn: Callable[[str], str] = click.secho, + confirm_fn: Callable[[str], bool] = click.confirm, + load_application_fn: Callable[[str], Application] = load_application, + get_aws_session_or_abort_fn: Callable[[str], Session] = get_aws_session_or_abort, + check_codebase_exists_fn: Callable[[str], str] = check_codebase_exists, + check_image_exists_fn: Callable[[str], str] = check_image_exists, + get_build_url_from_arn_fn: Callable[[str], str] = get_build_url_from_arn, + list_latest_images_fn: Callable[[str], str] = list_latest_images, + start_build_extraction_fn: Callable[[str], str] = start_build_extraction, + check_if_commit_exists_fn: Callable[[str], str] = check_if_commit_exists, + subprocess: Callable[[str], str] = subprocess.run, + ): + self.input_fn = input_fn + self.echo_fn = echo_fn + self.confirm_fn = confirm_fn + self.load_application_fn = load_application_fn + self.get_aws_session_or_abort_fn = get_aws_session_or_abort_fn + self.check_codebase_exists_fn = check_codebase_exists_fn + self.check_image_exists_fn = check_image_exists_fn + self.get_build_url_from_arn_fn = get_build_url_from_arn_fn + self.list_latest_images_fn = list_latest_images_fn + self.start_build_extraction_fn = start_build_extraction_fn + self.check_if_commit_exists_fn = check_if_commit_exists_fn + self.subprocess = subprocess + + def prepare(self): + """Sets up an application codebase for use within a DBT platform + project.""" + templates = setup_templates() + + repository = ( + self.subprocess(["git", "remote", "get-url", "origin"], capture_output=True, text=True) + .stdout.split("/")[-1] + .strip() + .removesuffix(".git") + ) + if repository.endswith("-deploy") or Path("./copilot").exists(): + raise NotInCodeBaseRepositoryError + + builder_configuration_url = "https://raw.githubusercontent.com/uktrade/ci-image-builder/main/image_builder/configuration/builder_configuration.yml" + builder_configuration_response = requests.get(builder_configuration_url) + builder_configuration_content = yaml.safe_load( + builder_configuration_response.content.decode("utf-8") + ) + builder_versions = next( + ( + item + for item in builder_configuration_content["builders"] + if item["name"] == "paketobuildpacks/builder-jammy-base" + ), + None, + ) + builder_version = max(x["version"] for x in builder_versions["versions"]) + builder_version = min(builder_version, "0.4.240") + + Path("./.copilot/phases").mkdir(parents=True, exist_ok=True) + image_build_run_contents = templates.get_template(f".copilot/image_build_run.sh").render() + + config_contents = templates.get_template(f".copilot/config.yml").render( + repository=repository, builder_version=builder_version + ) + self.echo_fn( + mkfile( + Path("."), ".copilot/image_build_run.sh", image_build_run_contents, overwrite=True + ) + ) + + image_build_run_file = Path(".copilot/image_build_run.sh") + image_build_run_file.chmod(image_build_run_file.stat().st_mode | stat.S_IEXEC) + + self.echo_fn(mkfile(Path("."), ".copilot/config.yml", config_contents, overwrite=True)) + + for phase in ["build", "install", "post_build", "pre_build"]: + phase_contents = templates.get_template(f".copilot/phases/{phase}.sh").render() + + self.echo_fn( + mkfile(Path("./.copilot"), f"phases/{phase}.sh", phase_contents, overwrite=True) + ) + + def build(self, app: str, codebase: str, commit: str): + """Trigger a CodePipeline pipeline based build.""" + session = self.get_aws_session_or_abort_fn() + self.load_application_fn(app, default_session=session) + + self.check_if_commit_exists_fn(commit) + + codebuild_client = session.client("codebuild") + build_url = self.__start_build_with_confirmation( + self.confirm_fn, + codebuild_client, + self.get_build_url_from_arn_fn, + f'You are about to build "{app}" for "{codebase}" with commit "{commit}". Do you want to continue?', + { + "projectName": f"codebuild-{app}-{codebase}", + "artifactsOverride": {"type": "NO_ARTIFACTS"}, + "sourceVersion": commit, + }, + ) + + if build_url: + return self.echo_fn( + f"Your build has been triggered. Check your build progress in the AWS Console: {build_url}" + ) + + raise ApplicationDeploymentNotTriggered() + + def deploy(self, app, env, codebase, commit): + """Trigger a CodePipeline pipeline based deployment.""" + session = self.get_aws_session_or_abort_fn() + + application = self.load_application_fn(app, default_session=session) + if not application.environments.get(env): + raise ApplicationEnvironmentNotFoundError() + + json.loads(self.check_codebase_exists_fn(session, application, codebase)) + + self.check_image_exists_fn(session, application, codebase, commit) + + codebuild_client = session.client("codebuild") + build_url = self.__start_build_with_confirmation( + self.confirm_fn, + codebuild_client, + self.get_build_url_from_arn_fn, + f'You are about to deploy "{app}" for "{codebase}" with commit "{commit}" to the "{env}" environment. Do you want to continue?', + { + "projectName": f"pipeline-{application.name}-{codebase}-BuildProject", + "artifactsOverride": {"type": "NO_ARTIFACTS"}, + "sourceTypeOverride": "NO_SOURCE", + "environmentVariablesOverride": [ + {"name": "COPILOT_ENVIRONMENT", "value": env}, + {"name": "IMAGE_TAG", "value": f"commit-{commit}"}, + ], + }, + ) + + if build_url: + return self.echo_fn( + "Your deployment has been triggered. Check your build progress in the AWS Console: " + f"{build_url}", + ) + + raise ApplicationDeploymentNotTriggered() + + def list(self, app: str, with_images: bool): + """List available codebases for the application.""" + session = self.get_aws_session_or_abort_fn() + application = self.load_application_fn(app, session) + ssm_client = session.client("ssm") + ecr_client = session.client("ecr") + codebases = self.__get_codebases(application, ssm_client) + + self.echo_fn("The following codebases are available:") + + for codebase in codebases: + self.echo_fn(f"- {codebase['name']} (https://github.com/{codebase['repository']})") + if with_images: + self.list_latest_images_fn( + ecr_client, + f"{application.name}/{codebase['name']}", + codebase["repository"], + self.echo_fn, + ) + + self.echo_fn("") + + # TODO return empty list without exception + def __get_codebases(self, application, ssm_client): + parameters = ssm_client.get_parameters_by_path( + Path=f"/copilot/applications/{application.name}/codebases", + Recursive=True, + )["Parameters"] + + codebases = [json.loads(p["Value"]) for p in parameters] + + if not codebases: + raise NoCopilotCodebasesFoundError + return codebases + + def __start_build_with_confirmation( + self, + confirm_fn, + codebuild_client, + get_build_url_from_arn_fn, + confirmation_message, + build_options, + ): + if confirm_fn(confirmation_message): + build_arn = self.start_build_extraction_fn(codebuild_client, build_options) + return get_build_url_from_arn_fn(build_arn) + return None diff --git a/dbt_platform_helper/domain/database_copy.py b/dbt_platform_helper/domain/database_copy.py index 7d9d85d45..a84cbc806 100644 --- a/dbt_platform_helper/domain/database_copy.py +++ b/dbt_platform_helper/domain/database_copy.py @@ -8,9 +8,9 @@ from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE from dbt_platform_helper.domain.maintenance_page import MaintenancePageProvider +from dbt_platform_helper.exceptions import ApplicationNotFoundError from dbt_platform_helper.exceptions import AWSException from dbt_platform_helper.utils.application import Application -from dbt_platform_helper.utils.application import ApplicationNotFoundError from dbt_platform_helper.utils.application import load_application from dbt_platform_helper.utils.aws import Vpc from dbt_platform_helper.utils.aws import get_connection_string diff --git a/dbt_platform_helper/exceptions.py b/dbt_platform_helper/exceptions.py index ffd204c41..b77561067 100644 --- a/dbt_platform_helper/exceptions.py +++ b/dbt_platform_helper/exceptions.py @@ -18,3 +18,31 @@ def __init__(self, app_version: str, check_version: str): super().__init__() self.app_version = app_version self.check_version = check_version + + +class CopilotCodebaseNotFoundError(Exception): + pass + + +class NotInCodeBaseRepositoryError(Exception): + pass + + +class NoCopilotCodebasesFoundError(Exception): + pass + + +class ImageNotFoundError(Exception): + pass + + +class ApplicationDeploymentNotTriggered(Exception): + pass + + +class ApplicationNotFoundError(Exception): + pass + + +class ApplicationEnvironmentNotFoundError(Exception): + pass diff --git a/dbt_platform_helper/templates/env/overrides/.gitignore b/dbt_platform_helper/templates/env/overrides/.gitignore deleted file mode 100644 index 3408a29a7..000000000 --- a/dbt_platform_helper/templates/env/overrides/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Copilot template communication with the CDK. -.build - -# NodeJS artifacts. -node_modules -*.js -!jest.config.js -*.d.ts - -# CDK asset staging directory. -.cdk.staging -cdk.out diff --git a/dbt_platform_helper/templates/env/overrides/README.md b/dbt_platform_helper/templates/env/overrides/README.md deleted file mode 100644 index d4184b867..000000000 --- a/dbt_platform_helper/templates/env/overrides/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Answers to some obvious questions - -## Why not regular cfn.patches.yml for the overrides? - -We need to add the contents of `extensions.yml` to Parameter Store. - -This is then used by is used by `platform-helper conduit` so that you don't have to be in the `*-deploy` directory to run it. - -## Why TypeScript and not Python? - -Because [TypeScript is what AWS Copilot supports](https://aws.github.io/copilot-cli/en/docs/developing/overrides/cdk/). diff --git a/dbt_platform_helper/templates/env/overrides/bin/override.ts b/dbt_platform_helper/templates/env/overrides/bin/override.ts deleted file mode 100644 index dded846b3..000000000 --- a/dbt_platform_helper/templates/env/overrides/bin/override.ts +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env node -import * as cdk from 'aws-cdk-lib'; -import { TransformedStack } from '../stack'; - -const app = new cdk.App(); -new TransformedStack(app, 'Stack', { - appName: process.env.COPILOT_APPLICATION_NAME || "", - envName: process.env.COPILOT_ENVIRONMENT_NAME || "", -}); diff --git a/dbt_platform_helper/templates/env/overrides/cdk.json b/dbt_platform_helper/templates/env/overrides/cdk.json deleted file mode 100644 index df4f7ed45..000000000 --- a/dbt_platform_helper/templates/env/overrides/cdk.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/override.ts", - "versionReporting": false, - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - } -} diff --git a/dbt_platform_helper/templates/env/overrides/log_resource_policy.json b/dbt_platform_helper/templates/env/overrides/log_resource_policy.json deleted file mode 100644 index 42a6ae077..000000000 --- a/dbt_platform_helper/templates/env/overrides/log_resource_policy.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "StateMachineToCloudWatchLogs123", - "Effect": "Allow", - "Principal": { - "Service": [ - "delivery.logs.amazonaws.com" - ] - }, - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": [ - "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/copilot/${AppName}-${EnvironmentName}-*:log-stream:*" - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": "${AWS::AccountId}" - }, - "ArnLike": { - "aws:SourceArn": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "delivery.logs.amazonaws.com" - ] - }, - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/elasticache/${AppName}/${EnvironmentName}/*", - "Condition": { - "StringEquals": { - "aws:SourceAccount": "${AWS::AccountId}" - }, - "ArnLike": { - "aws:SourceArn": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "es.amazonaws.com" - ] - }, - "Action": [ - "logs:PutLogEvents", - "logs:CreateLogStream" - ], - "Resource": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/opensearch/${AppName}/${EnvironmentName}/*", - "Condition": { - "StringEquals": { - "aws:SourceAccount": "${AWS::AccountId}" - } - } - } - ] -} diff --git a/dbt_platform_helper/templates/env/overrides/package-lock.json b/dbt_platform_helper/templates/env/overrides/package-lock.json deleted file mode 100644 index 1d5320642..000000000 --- a/dbt_platform_helper/templates/env/overrides/package-lock.json +++ /dev/null @@ -1,4307 +0,0 @@ -{ - "name": "override", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "override", - "version": "0.1.0", - "dependencies": { - "aws-cdk-lib": "2.137.0", - "constructs": "^10.0.0", - "source-map-support": "^0.5.21", - "yaml": "^2.3.4" - }, - "bin": { - "override": "bin/override.js" - }, - "devDependencies": { - "@types/jest": "^29.2.4", - "@types/node": "18.11.15", - "aws-cdk": "2.137.0", - "jest": "^29.3.1", - "ts-jest": "^29.0.3", - "ts-node": "^10.9.1", - "typescript": "~4.9.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.3.tgz", - "integrity": "sha512-twhuEG+JPOYCYPx/xy5uH2+VUsIEhPTzDY0F1KuB+ocjWWB/KEDiOVL19nHvbPCB6fhWnkykXEMJ4HHcKvjtvg==" - }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "18.11.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz", - "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aws-cdk": { - "version": "2.137.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.137.0.tgz", - "integrity": "sha512-3pf3SVDwNZvo3EfhO3yl1B+KbRHz7T4UmPifUEKfOwk7ABAFLRSNddZuUlF560XSBTFLkrZoeBDa0/MLJT6F4g==", - "dev": true, - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 14.15.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.137.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.137.0.tgz", - "integrity": "sha512-pD3AGdKBa8q1+vVWRabiDHuecVMlP8ERGPHc9Pb0dVlpbC/ODC6XXC1S0TAMsr0JI5Lh6pk4vL5cC+spsMeotw==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.1", - "jsonschema": "^1.4.1", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.6.0", - "table": "^6.8.2", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "constructs": "^10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.12.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/astral-regex": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/lru-cache": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/require-from-string": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.6.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/slice-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.2", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/uri-js": { - "version": "4.4.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001582", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001582.tgz", - "integrity": "sha512-vsJG3V5vgfduaQGVxL53uSX/HUzxyr2eA8xCo36OLal7sRcSZbibJtLeh0qja4sFOr/QQGt4opB4tOy+eOgAxg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/constructs": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", - "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", - "engines": { - "node": ">= 16.14.0" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.653", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.653.tgz", - "integrity": "sha512-wA2A2LQCqnEwQAvwADQq3KpMpNwgAUBnRmrFgRzHnPhbQUFArTR32Ab46f4p0MovDLcg4uqd4nCsN2hTltslpA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/dbt_platform_helper/templates/env/overrides/package.json b/dbt_platform_helper/templates/env/overrides/package.json deleted file mode 100644 index 046e0adfa..000000000 --- a/dbt_platform_helper/templates/env/overrides/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "override", - "version": "0.1.0", - "bin": { - "override": "bin/override.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "cdk": "cdk" - }, - "devDependencies": { - "@types/jest": "^29.2.4", - "@types/node": "18.11.15", - "jest": "^29.3.1", - "ts-jest": "^29.0.3", - "aws-cdk": "2.137.0", - "ts-node": "^10.9.1", - "typescript": "~4.9.4" - }, - "dependencies": { - "aws-cdk-lib": "2.137.0", - "constructs": "^10.0.0", - "source-map-support": "^0.5.21", - "yaml": "^2.3.4" - } -} diff --git a/dbt_platform_helper/templates/env/overrides/stack.ts b/dbt_platform_helper/templates/env/overrides/stack.ts deleted file mode 100644 index 6e8f69e92..000000000 --- a/dbt_platform_helper/templates/env/overrides/stack.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { aws_cloudformation as cloudformation, aws_logs as logs, Fn } from 'aws-cdk-lib'; -import * as path from 'path'; -import resource_policy from './log_resource_policy.json'; -import { readFileSync } from "fs"; -import { parse } from 'yaml'; - -interface TransformedStackProps extends cdk.StackProps { - readonly appName: string; - readonly envName: string; -} - -export class TransformedStack extends cdk.Stack { - public readonly template: cdk.cloudformation_include.CfnInclude; - public readonly appName: string; - public readonly envName: string; - - constructor(scope: cdk.App, id: string, props: TransformedStackProps) { - super(scope, id, props); - this.template = new cdk.cloudformation_include.CfnInclude(this, 'Template', { - templateFile: path.join('.build', 'in.yml'), - }); - this.appName = props.appName; - this.envName = props.envName; - - this.addLogResourcePolicy(); - this.uploadAddonConfiguration(); - } - - private addLogResourcePolicy() { - const addonStack = this.template.getResource("AddonsStack") as cloudformation.CfnStack; - const logResourcePolicy = this.template.getResource("LogResourcePolicy") as logs.CfnResourcePolicy; - - addonStack.addDependency(logResourcePolicy); - logResourcePolicy.policyDocument = Fn.sub(JSON.stringify(resource_policy)); - } - - private uploadAddonConfiguration() { - const deployRepoRoot = path.join(process.cwd(), '..', '..', '..'); - - const addonConfig = parse(readFileSync( - path.join(deployRepoRoot, 'extensions.yml'), - ).toString('utf-8')); - - new cdk.aws_ssm.CfnParameter(this, 'AddonConfig', { - name: `/copilot/applications/${this.appName}/environments/${this.envName}/addons`, - type: "String", - value: JSON.stringify(addonConfig), - }); - } -} diff --git a/dbt_platform_helper/templates/env/overrides/tsconfig.json b/dbt_platform_helper/templates/env/overrides/tsconfig.json deleted file mode 100644 index d59b15f9d..000000000 --- a/dbt_platform_helper/templates/env/overrides/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": [ - "es2020" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "resolveJsonModule": true, - "esModuleInterop": true, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/dbt_platform_helper/utils/application.py b/dbt_platform_helper/utils/application.py index 5717b1db8..420689df5 100644 --- a/dbt_platform_helper/utils/application.py +++ b/dbt_platform_helper/utils/application.py @@ -8,6 +8,7 @@ from boto3 import Session from yaml.parser import ParserError +from dbt_platform_helper.exceptions import ApplicationNotFoundError from dbt_platform_helper.utils.aws import get_aws_session_or_abort from dbt_platform_helper.utils.aws import get_profile_name_from_account_id from dbt_platform_helper.utils.aws import get_ssm_secrets @@ -67,10 +68,6 @@ def __eq__(self, other): return str(self) == str(other) -class ApplicationNotFoundError(Exception): - pass - - def load_application(app: str = None, default_session: Session = None) -> Application: application = Application(app if app else get_application_name()) current_session = default_session if default_session else get_aws_session_or_abort() diff --git a/dbt_platform_helper/utils/aws.py b/dbt_platform_helper/utils/aws.py index bd5cadb62..0ac6399af 100644 --- a/dbt_platform_helper/utils/aws.py +++ b/dbt_platform_helper/utils/aws.py @@ -13,7 +13,12 @@ from boto3 import Session from dbt_platform_helper.exceptions import AWSException +from dbt_platform_helper.exceptions import CopilotCodebaseNotFoundError +from dbt_platform_helper.exceptions import ImageNotFoundError from dbt_platform_helper.exceptions import ValidationException +from dbt_platform_helper.utils.files import cache_refresh_required +from dbt_platform_helper.utils.files import read_supported_versions_from_cache +from dbt_platform_helper.utils.files import write_to_cache SSM_BASE_PATH = "/copilot/{app}/{env}/secrets/" SSM_PATH = "/copilot/{app}/{env}/secrets/{name}" @@ -351,6 +356,59 @@ def get_postgres_connection_data_updated_with_master_secret(session, parameter_n return parameter_data +def get_supported_redis_versions(): + + if cache_refresh_required("redis"): + + supported_versions = [] + + session = get_aws_session_or_abort() + elasticache_client = session.client("elasticache") + + supported_versions_response = elasticache_client.describe_cache_engine_versions( + Engine="redis" + ) + + supported_versions = [ + version["EngineVersion"] + for version in supported_versions_response["CacheEngineVersions"] + ] + + write_to_cache("redis", supported_versions) + + return supported_versions + + else: + return read_supported_versions_from_cache("redis") + + +def get_supported_opensearch_versions(): + + if cache_refresh_required("opensearch"): + + supported_versions = [] + + session = get_aws_session_or_abort() + opensearch_client = session.client("opensearch") + + response = opensearch_client.list_versions() + all_versions = response["Versions"] + + opensearch_versions = [ + version for version in all_versions if not version.startswith("Elasticsearch_") + ] + supported_versions = [ + version.removeprefix("OpenSearch_") for version in opensearch_versions + ] + + write_to_cache("opensearch", supported_versions) + + return supported_versions + + else: + return read_supported_versions_from_cache("opensearch") + + def get_connection_string( session: Session, app: str, @@ -420,3 +478,77 @@ def get_vpc_info_by_name(session: Session, app: str, env: str, vpc_name: str) -> raise AWSException(f"No matching security groups found in vpc '{vpc_name}'") return Vpc(subnets, sec_groups) + + +def start_build_extraction(codebuild_client, build_options): + response = codebuild_client.start_build(**build_options) + return response["build"]["arn"] + + +def check_codebase_exists(session: Session, application, codebase: str): + try: + ssm_client = session.client("ssm") + ssm_client.get_parameter( + Name=f"/copilot/applications/{application.name}/codebases/{codebase}" + )["Parameter"]["Value"] + except ( + KeyError, + ValueError, + ssm_client.exceptions.ParameterNotFound, + ): + raise CopilotCodebaseNotFoundError + + +def check_image_exists(session, application, codebase, commit): + ecr_client = session.client("ecr") + try: + ecr_client.describe_images( + repositoryName=f"{application.name}/{codebase}", + imageIds=[{"imageTag": f"commit-{commit}"}], + ) + except ( + ecr_client.exceptions.RepositoryNotFoundException, + ecr_client.exceptions.ImageNotFoundException, + ): + raise ImageNotFoundError + + +def get_build_url_from_arn(build_arn: str) -> str: + _, _, _, region, account_id, project_name, build_id = build_arn.split(":") + project_name = project_name.removeprefix("build/") + return ( + f"https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/{account_id}/projects/" + f"{project_name}/build/{project_name}%3A{build_id}" + ) + + +def list_latest_images(ecr_client, ecr_repository_name, codebase_repository, echo_fn): + paginator = ecr_client.get_paginator("describe_images") + describe_images_response_iterator = paginator.paginate( + repositoryName=ecr_repository_name, + filter={"tagStatus": "TAGGED"}, + ) + images = [] + for page in describe_images_response_iterator: + images += page["imageDetails"] + + sorted_images = sorted( + images, + key=lambda i: i["imagePushedAt"], + reverse=True, + ) + + MAX_RESULTS = 20 + + for image in sorted_images[:MAX_RESULTS]: + try: + commit_tag = next(t for t in image["imageTags"] if t.startswith("commit-")) + if not commit_tag: + continue + + commit_hash = commit_tag.replace("commit-", "") + echo_fn( + f" - https://github.com/{codebase_repository}/commit/{commit_hash} - published: {image['imagePushedAt']}" + ) + except StopIteration: + continue diff --git a/dbt_platform_helper/utils/files.py b/dbt_platform_helper/utils/files.py index 6cf343f8a..553f1c3ea 100644 --- a/dbt_platform_helper/utils/files.py +++ b/dbt_platform_helper/utils/files.py @@ -1,4 +1,6 @@ +import os from copy import deepcopy +from datetime import datetime from os import makedirs from pathlib import Path @@ -7,6 +9,8 @@ from jinja2 import Environment from jinja2 import FileSystemLoader +from dbt_platform_helper.constants import PLATFORM_HELPER_CACHE_FILE + def to_yaml(value): return yaml.dump(value, sort_keys=False) @@ -102,3 +106,69 @@ def combine_env_data(data): enriched_config["environments"] = defaulted_envs return enriched_config + + +def read_supported_versions_from_cache(resource_name): + + platform_helper_config = read_file_as_yaml(PLATFORM_HELPER_CACHE_FILE) + + return platform_helper_config.get(resource_name).get("versions") + + +def write_to_cache(resource_name, supported_versions): + + platform_helper_config = {} + + if os.path.exists(PLATFORM_HELPER_CACHE_FILE): + platform_helper_config = read_file_as_yaml(PLATFORM_HELPER_CACHE_FILE) + + cache_dict = { + resource_name: { + "versions": supported_versions, + "date-retrieved": datetime.now().strftime("%d-%m-%y %H:%M:%S"), + } + } + + platform_helper_config.update(cache_dict) + + with open(PLATFORM_HELPER_CACHE_FILE, "w") as file: + file.write("# [!] This file is autogenerated via the platform-helper. Do not edit.\n") + yaml.dump(platform_helper_config, file) + + +def cache_refresh_required(resource_name) -> bool: + """ + Checks if the platform-helper should reach out to AWS to 'refresh' its + cached values. + + An API call is needed if any of the following conditions are met: + 1. No cache file (.platform-helper-config.yml) exists. + 2. The resource name (e.g. redis, opensearch) does not exist within the cache file. + 3. The date-retrieved value of the cached data is > than a time interval. In this case 1 day. + """ + + if not os.path.exists(PLATFORM_HELPER_CACHE_FILE): + return True + + platform_helper_config = read_file_as_yaml(PLATFORM_HELPER_CACHE_FILE) + + if platform_helper_config.get(resource_name): + return check_if_cached_datetime_is_greater_than_interval( + platform_helper_config[resource_name].get("date-retrieved"), 1 + ) + + return True + + +def check_if_cached_datetime_is_greater_than_interval(date_retrieved, interval_in_days): + + current_datetime = datetime.now() + cached_datetime = datetime.strptime(date_retrieved, "%d-%m-%y %H:%M:%S") + delta = current_datetime - cached_datetime + + return delta.days > interval_in_days + + +def read_file_as_yaml(file_name): + + return yaml.safe_load(Path(file_name).read_text()) diff --git a/dbt_platform_helper/utils/git.py b/dbt_platform_helper/utils/git.py index 12cf035d3..e451ce652 100644 --- a/dbt_platform_helper/utils/git.py +++ b/dbt_platform_helper/utils/git.py @@ -2,6 +2,10 @@ import subprocess +class CommitNotFoundError(Exception): + pass + + def git_remote(): git_repo = subprocess.run( ["git", "remote", "get-url", "origin"], capture_output=True, text=True @@ -14,3 +18,12 @@ def extract_repository_name(repository_url): return return re.search(r"([^/:]*/[^/]*)\.git", repository_url).group(1) + + +def check_if_commit_exists(commit): + branches_containing_commit = subprocess.run( + ["git", "branch", "-r", "--contains", f"{commit}"], capture_output=True, text=True + ) + + if branches_containing_commit.stderr: + raise CommitNotFoundError() diff --git a/dbt_platform_helper/utils/validation.py b/dbt_platform_helper/utils/validation.py index 52dec7273..454142879 100644 --- a/dbt_platform_helper/utils/validation.py +++ b/dbt_platform_helper/utils/validation.py @@ -18,6 +18,8 @@ from dbt_platform_helper.constants import ENVIRONMENTS_KEY from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE from dbt_platform_helper.constants import PLATFORM_HELPER_VERSION_FILE +from dbt_platform_helper.utils.aws import get_supported_opensearch_versions +from dbt_platform_helper.utils.aws import get_supported_redis_versions from dbt_platform_helper.utils.files import apply_environment_defaults from dbt_platform_helper.utils.messages import abort_with_error @@ -98,6 +100,19 @@ def validate_addons(addons: dict): except SchemaError as ex: errors[addon_name] = f"Error in {addon_name}: {ex.code}" + _validate_extension_supported_versions( + config={"extensions": addons}, + extension_type="redis", + version_key="engine", + get_supported_versions_fn=get_supported_redis_versions, + ) + _validate_extension_supported_versions( + config={"extensions": addons}, + extension_type="opensearch", + version_key="engine", + get_supported_versions_fn=get_supported_opensearch_versions, + ) + return errors @@ -152,7 +167,7 @@ def is_between(value): "x-large-ha", ) -REDIS_ENGINE_VERSIONS = Or("6.2", "7.0", "7.1") +REDIS_ENGINE_VERSIONS = str REDIS_DEFINITION = { "type": "redis", @@ -300,7 +315,7 @@ def iam_role_arn_regex(key): OPENSEARCH_PLANS = Or( "tiny", "small", "small-ha", "medium", "medium-ha", "large", "large-ha", "x-large", "x-large-ha" ) -OPENSEARCH_ENGINE_VERSIONS = Or("2.11", "2.9", "2.7", "2.5", "2.3", "1.3", "1.2", "1.1", "1.0") +OPENSEARCH_ENGINE_VERSIONS = str OPENSEARCH_MIN_VOLUME_SIZE = 10 OPENSEARCH_MAX_VOLUME_SIZE = { "tiny": 100, @@ -489,6 +504,60 @@ def validate_platform_config(config): _validate_codebase_pipelines(enriched_config) validate_database_copy_section(enriched_config) + _validate_extension_supported_versions( + config=config, + extension_type="redis", + version_key="engine", + get_supported_versions_fn=get_supported_redis_versions, + ) + _validate_extension_supported_versions( + config=config, + extension_type="opensearch", + version_key="engine", + get_supported_versions_fn=get_supported_opensearch_versions, + ) + + +def _validate_extension_supported_versions( + config, extension_type, version_key, get_supported_versions_fn +): + + extensions = config.get("extensions", {}) + if not extensions: + return + + extensions_for_type = [ + extension + for extension in config.get("extensions", {}).values() + if extension.get("type") == extension_type + ] + + supported_extension_versions = get_supported_versions_fn() + extensions_with_invalid_version = [] + + for extension in extensions_for_type: + + environments = extension.get("environments", {}) + + if not isinstance(environments, dict): + click.secho( + "Error: Opensearch extension definition is invalid type, expected dictionary", + fg="red", + ) + continue + for environment, env_config in environments.items(): + extension_version = env_config.get(version_key) + if extension_version not in supported_extension_versions: + extensions_with_invalid_version.append( + {"environment": environment, "version": extension_version} + ) + + for version_failure in extensions_with_invalid_version: + click.secho( + f"{extension_type} version for environment {version_failure['environment']} is not in the list of supported {extension_type} versions: {supported_extension_versions}. Provided Version: {version_failure['version']}", + fg="red", + ) + def validate_database_copy_section(config): extensions = config.get("extensions", {}) diff --git a/tests/platform_helper/conftest.py b/tests/platform_helper/conftest.py index 23e37c379..65324229f 100644 --- a/tests/platform_helper/conftest.py +++ b/tests/platform_helper/conftest.py @@ -687,3 +687,23 @@ def create_invalid_platform_config_file(fakefs): Path(PLATFORM_CONFIG_FILE), contents=INVALID_PLATFORM_CONFIG_WITH_PLATFORM_VERSION_OVERRIDES, ) + + +@pytest.fixture(autouse=True) +def mock_get_supported_opensearch_versions(monkeypatch): + def mock_return_value(opensearch_client=None): + return ["1.0", "1.1", "1.2"] + + monkeypatch.setattr( + "dbt_platform_helper.utils.validation.get_supported_opensearch_versions", mock_return_value + ) + + +@pytest.fixture(autouse=True) +def mock_get_supported_redis_versions(monkeypatch): + def mock_return_value(opensearch_client=None): + return ["6.2", "7.0", "7.1"] + + monkeypatch.setattr( + "dbt_platform_helper.utils.validation.get_supported_redis_versions", mock_return_value + ) diff --git a/tests/platform_helper/domain/test_codebase.py b/tests/platform_helper/domain/test_codebase.py new file mode 100644 index 000000000..469aef8a6 --- /dev/null +++ b/tests/platform_helper/domain/test_codebase.py @@ -0,0 +1,644 @@ +import filecmp +import json +import os +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import PropertyMock +from unittest.mock import call +from unittest.mock import patch + +import boto3 +import pytest +import requests + +from dbt_platform_helper.domain.codebase import Codebase +from dbt_platform_helper.domain.codebase import NotInCodeBaseRepositoryError +from dbt_platform_helper.exceptions import ApplicationDeploymentNotTriggered +from dbt_platform_helper.exceptions import ApplicationEnvironmentNotFoundError +from dbt_platform_helper.exceptions import ApplicationNotFoundError +from dbt_platform_helper.exceptions import CopilotCodebaseNotFoundError +from dbt_platform_helper.exceptions import ImageNotFoundError +from dbt_platform_helper.exceptions import NoCopilotCodebasesFoundError +from dbt_platform_helper.utils.application import Environment +from dbt_platform_helper.utils.git import CommitNotFoundError +from tests.platform_helper.conftest import EXPECTED_FILES_DIR + +ecr_exceptions = boto3.client("ecr").exceptions +ssm_exceptions = boto3.client("ssm").exceptions + + +def mock_aws_client(get_aws_session_or_abort): + session = MagicMock(name="session-mock") + client = MagicMock(name="client-mock") + session.client.return_value = client + get_aws_session_or_abort.return_value = session + return client + + +class CodebaseMocks: + def __init__(self, **kwargs): + self.load_application_fn = kwargs.get("load_application_fn", Mock()) + self.get_aws_session_or_abort_fn = kwargs.get("get_aws_session_or_abort_fn", Mock()) + self.input_fn = kwargs.get("input_fn", Mock(return_value="yes")) + self.echo_fn = kwargs.get("echo_fn", Mock()) + self.confirm_fn = kwargs.get("confirm_fn", Mock(return_value=True)) + self.check_codebase_exists_fn = kwargs.get( + "check_codebase_exists_fn", + Mock( + return_value=""" + { + "name": "test-app", + "repository": "uktrade/test-app", + "services": "1234" + } + """ + ), + ) + self.check_image_exists_fn = kwargs.get("check_image_exists_fn", Mock(return_value="")) + self.subprocess = kwargs.get("subprocess", Mock()) + self.check_if_commit_exists_fn = kwargs.get("check_if_commit_exists_fn", Mock()) + + def params(self): + return { + "load_application_fn": self.load_application_fn, + "get_aws_session_or_abort_fn": self.get_aws_session_or_abort_fn, + "check_codebase_exists_fn": self.check_codebase_exists_fn, + "check_image_exists_fn": self.check_image_exists_fn, + "input_fn": self.input_fn, + "echo_fn": self.echo_fn, + "confirm_fn": self.confirm_fn, + "subprocess": self.subprocess, + "check_if_commit_exists_fn": self.check_if_commit_exists_fn, + } + + +@patch("requests.get") +def test_codebase_prepare_generates_the_expected_files(mocked_requests_get, tmp_path): + mocks = CodebaseMocks() + codebase = Codebase(**mocks.params()) + mocked_response_content = """ + builders: + - name: paketobuildpacks/builder-jammy-full + versions: + - version: 0.3.294 + - version: 0.3.288 + - name: paketobuildpacks/builder-jammy-base + versions: + - version: 0.1.234 + - version: 0.5.678 + - name: paketobuildpacks/builder + deprecated: true + versions: + - version: 0.2.443-full + """ + + def mocked_response(): + r = requests.Response() + r.status_code = 200 + type(r).content = PropertyMock(return_value=mocked_response_content.encode("utf-8")) + + return r + + mocked_requests_get.return_value = mocked_response() + + os.chdir(tmp_path) + + mocks.subprocess.return_value.stdout = "git@github.com:uktrade/test-app.git" + + codebase.prepare() + + expected_files_dir = Path(EXPECTED_FILES_DIR) / ".copilot" + copilot_dir = Path.cwd() / ".copilot" + + compare_directories = filecmp.dircmp(str(expected_files_dir), str(copilot_dir)) + + mocks.echo_fn.assert_has_calls( + [ + call( + "File .copilot/image_build_run.sh created", + ), + call( + "File .copilot/config.yml created", + ), + call( + "File phases/build.sh created", + ), + call( + "File phases/install.sh created", + ), + call( + "File phases/post_build.sh created", + ), + call( + "File phases/pre_build.sh created", + ), + ] + ) + + assert is_same_files(compare_directories) is True + + +def test_codebase_prepare_does_not_generate_files_in_a_repo_with_a_copilot_directory(tmp_path): + mocks = CodebaseMocks() + mocks.load_application_fn.side_effect = SystemExit(1) + codebase = Codebase(**mocks.params()) + os.chdir(tmp_path) + Path(tmp_path / "copilot").mkdir() + + mocks.subprocess.return_value.stderr = mock_suprocess_fixture() + + codebase.prepare() + + mocks.echo_fn.assert_has_calls( + [ + call( + "You are in the deploy repository; make sure you are in the application codebase repository.", + ), + ] + ) + + +def test_codebase_build_does_not_trigger_build_without_an_application(): + mocks = CodebaseMocks() + mocks.load_application_fn.side_effect = ApplicationNotFoundError() + codebase = Codebase(**mocks.params()) + + with pytest.raises(ApplicationNotFoundError): + codebase.build("not-an-application", "application", "ab1c23d") + mocks.echo_fn.assert_has_calls( + [ + call( + """The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""", + fg="red", + ), + ] + ) + + +def test_codebase_build_commit_not_found(): + mocks = CodebaseMocks(check_if_commit_exists_fn=Mock(side_effect=CommitNotFoundError())) + + codebase = Codebase(**mocks.params()) + + with pytest.raises(CommitNotFoundError): + codebase.build("not-an-application", "application", "ab1c23d") + + +def test_codebase_prepare_does_not_generate_files_in_a_repo_with_a_copilot_directory(tmp_path): + mocks = CodebaseMocks() + mocks.load_application_fn.side_effect = SystemExit(1) + + mocks.subprocess.return_value = mock_suprocess_fixture() + codebase = Codebase(**mocks.params()) + os.chdir(tmp_path) + Path(tmp_path / "copilot").mkdir() + + with pytest.raises(NotInCodeBaseRepositoryError): + codebase.prepare() + + +def test_codebase_prepare_generates_an_executable_image_build_run_file(tmp_path): + mocks = CodebaseMocks() + codebase = Codebase(**mocks.params()) + os.chdir(tmp_path) + mocks.subprocess.return_value.stdout = "demodjango" + + codebase.prepare() + + image_build_run_path = Path(".copilot/image_build_run.sh") + assert image_build_run_path.exists() + assert image_build_run_path.is_file() + assert os.access(image_build_run_path, os.X_OK) + + +def test_codebase_build_does_not_trigger_deployment_without_confirmation(): + mocks = CodebaseMocks(confirm_fn=Mock(return_value=False)) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + + with pytest.raises(ApplicationDeploymentNotTriggered) as exc: + codebase = Codebase(**mocks.params()) + codebase.build("test-application", "application", "ab1c234") + + +def test_codebase_deploy_successfully_triggers_a_pipeline_based_deploy(mock_application): + mocks = CodebaseMocks() + mocks.confirm_fn.return_value = True + mock_application.environments = { + "development": Environment( + name="development", + account_id="1234", + sessions={"111111111111": mocks.get_aws_session_or_abort_fn}, + ) + } + mocks.load_application_fn.return_value = mock_application + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + client.start_build.return_value = { + "build": { + "arn": "arn:aws:codebuild:eu-west-2:111111111111:build/build-project:build-id", + }, + } + + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "ab1c23d") + + client.start_build.assert_called_with( + projectName="pipeline-test-application-application-BuildProject", + artifactsOverride={"type": "NO_ARTIFACTS"}, + sourceTypeOverride="NO_SOURCE", + environmentVariablesOverride=[ + {"name": "COPILOT_ENVIRONMENT", "value": "development"}, + {"name": "IMAGE_TAG", "value": "commit-ab1c23d"}, + ], + ) + + mocks.confirm_fn.assert_has_calls( + [ + call( + 'You are about to deploy "test-application" for "application" with commit ' + '"ab1c23d" to the "development" environment. Do you want to continue?' + ), + ] + ) + + mocks.echo_fn.assert_has_calls( + [ + call( + "Your deployment has been triggered. Check your build progress in the AWS Console: " + "https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/111111111111/projects/build" + "-project/build/build-project%3Abuild-id" + ) + ] + ) + + +def test_codebase_deploy_exception_with_a_nonexistent_codebase(): + mocks = CodebaseMocks(check_codebase_exists_fn=Mock(side_effect=CopilotCodebaseNotFoundError())) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + + with pytest.raises(CopilotCodebaseNotFoundError): + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "nonexistent-commit-hash") + + +def test_codebase_deploy_exception_with_malformed_json(): + mocks = CodebaseMocks(check_codebase_exists_fn=Mock(return_value="{ mlaf = josn}")) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + + with pytest.raises(json.JSONDecodeError): + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "nonexistent-commit-hash") + + +def test_codebase_deploy_aborts_with_a_nonexistent_image_repository(): + mocks = CodebaseMocks(check_image_exists_fn=Mock(side_effect=ImageNotFoundError)) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + client.describe_images.side_effect = ecr_exceptions.RepositoryNotFoundException({}, "") + + with pytest.raises(ImageNotFoundError): + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "nonexistent-commit-hash") + + +def test_codebase_deploy_aborts_with_a_nonexistent_image_tag(): + mocks = CodebaseMocks(check_image_exists_fn=Mock(side_effect=ImageNotFoundError)) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + client.describe_images.side_effect = ecr_exceptions.ImageNotFoundException({}, "") + + with pytest.raises(ImageNotFoundError): + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "nonexistent-commit-hash") + + +def test_codebase_deploy_does_not_trigger_build_without_confirmation(): + mocks = CodebaseMocks() + mocks.subprocess.return_value.stderr = "" + mocks.confirm_fn.return_value = False + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + client.exceptions.ParameterNotFound = ssm_exceptions.ParameterNotFound + client.start_build.return_value = { + "build": { + "arn": "arn:aws:codebuild:eu-west-2:111111111111:build/build-project:build-id", + }, + } + + with pytest.raises(ApplicationDeploymentNotTriggered) as exc: + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "ab1c23d") + + mocks.confirm_fn.assert_has_calls( + [ + call( + 'You are about to deploy "test-application" for "application" with commit ' + '"ab1c23d" to the "development" environment. Do you want to continue?' + ), + ] + ) + + mocks.echo_fn.assert_has_calls([call("Your deployment was not triggered.")]) + + +def test_codebase_deploy_does_not_trigger_build_without_an_application(): + mocks = CodebaseMocks() + mocks.load_application_fn.side_effect = ApplicationNotFoundError() + codebase = Codebase(**mocks.params()) + + with pytest.raises(ApplicationNotFoundError) as exc: + codebase.deploy("not-an-application", "dev", "application", "ab1c23d") + # TODO review + mocks.echo_fn.assert_has_calls( + [ + call( + """The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""", + fg="red", + ), + ] + ) + + +def test_codebase_deploy_does_not_trigger_build_with_missing_environment(mock_application): + mocks = CodebaseMocks() + mock_application.environments = {} + mocks.load_application_fn.return_value = mock_application + codebase = Codebase(**mocks.params()) + + with pytest.raises(ApplicationEnvironmentNotFoundError) as exc: + codebase.deploy("test-application", "not-an-environment", "application", "ab1c23d") + mocks.echo_fn.assert_has_calls( + [ + call( + """The environment "not-an-environment" either does not exist or has not been deployed.""", + fg="red", + ), + ] + ) + + +def test_codebase_deploy_does_not_trigger_deployment_without_confirmation(): + mocks = CodebaseMocks(confirm_fn=Mock(return_value=False)) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + + with pytest.raises(ApplicationDeploymentNotTriggered) as exc: + codebase = Codebase(**mocks.params()) + codebase.deploy("test-application", "development", "application", "nonexistent-commit-hash") + + +def test_codebase_list_does_not_trigger_build_without_an_application(): + mocks = CodebaseMocks() + mocks.load_application_fn.side_effect = ApplicationNotFoundError() + codebase = Codebase(**mocks.params()) + + with pytest.raises(ApplicationNotFoundError) as exc: + codebase.list("not-an-application", True) + + +def test_codebase_list_raises_exception_when_no_codebases(): + mocks = CodebaseMocks(check_codebase_exists_fn=Mock(side_effect=NoCopilotCodebasesFoundError())) + + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + + client.get_parameter.return_value = { + "Parameter": {"Value": json.dumps({"name": "application"})}, + } + + with pytest.raises(NoCopilotCodebasesFoundError): + codebase = Codebase(**mocks.params()) + codebase.list("test-application", True) + + +def test_lists_codebases_with_multiple_pages_of_images(): + mocks = CodebaseMocks() + codebase = Codebase(**mocks.params()) + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + client.get_parameters_by_path.return_value = { + "Parameters": [ + {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} + ], + } + + client.get_paginator.return_value.paginate.return_value = [ + { + "imageDetails": [ + { + "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-10"], + "imagePushedAt": datetime(2023, 11, 10, 00, 00, 00), + }, + { + "imageTags": ["branch-main", "commit-9"], + "imagePushedAt": datetime(2023, 11, 9, 00, 00, 00), + }, + ] + }, + { + "imageDetails": [ + { + "imageTags": ["commit-8"], + "imagePushedAt": datetime(2023, 11, 8, 00, 00, 00), + }, + { + "imageTags": ["commit-7"], + "imagePushedAt": datetime(2023, 11, 7, 00, 00, 00), + }, + ] + }, + ] + codebase.list("test-application", True) + + mocks.echo_fn.assert_has_calls( + [ + call("- application (https://github.com/uktrade/example)"), + call( + " - https://github.com/uktrade/example/commit/10 - published: 2023-11-10 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/9 - published: 2023-11-09 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/8 - published: 2023-11-08 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/7 - published: 2023-11-07 00:00:00" + ), + call(""), + ] + ) + + +def test_lists_codebases_with_disordered_images_in_chronological_order(): + mocks = CodebaseMocks() + codebase = Codebase(**mocks.params()) + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + client.get_parameters_by_path.return_value = { + "Parameters": [ + {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} + ], + } + client.get_paginator.return_value.paginate.return_value = [ + { + "imageDetails": [ + { + "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-4"], + "imagePushedAt": datetime(2023, 11, 4, 00, 00, 00), + }, + { + "imageTags": ["branch-main", "commit-2"], + "imagePushedAt": datetime(2023, 11, 2, 00, 00, 00), + }, + ] + }, + { + "imageDetails": [ + { + "imageTags": ["commit-1"], + "imagePushedAt": datetime(2023, 11, 1, 00, 00, 00), + }, + { + "imageTags": ["commit-3"], + "imagePushedAt": datetime(2023, 11, 3, 00, 00, 00), + }, + ] + }, + ] + codebase.list("test-application", True) + + mocks.echo_fn.assert_has_calls( + [ + call("The following codebases are available:"), + call("- application (https://github.com/uktrade/example)"), + call( + " - https://github.com/uktrade/example/commit/4 - published: 2023-11-04 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/3 - published: 2023-11-03 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/2 - published: 2023-11-02 00:00:00" + ), + call( + " - https://github.com/uktrade/example/commit/1 - published: 2023-11-01 00:00:00" + ), + ] + ) + + +def test_lists_codebases_with_images_successfully(): + mocks = CodebaseMocks() + codebase = Codebase(**mocks.params()) + client = mock_aws_client(mocks.get_aws_session_or_abort_fn) + client.get_parameters_by_path.return_value = { + "Parameters": [ + {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} + ], + } + client.get_paginator.return_value.paginate.return_value = [ + { + "imageDetails": [ + { + "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-ee4a82c"], + "imagePushedAt": datetime(2023, 11, 8, 17, 55, 35), + }, + { + "imageTags": ["branch-main", "commit-d269d51"], + "imagePushedAt": datetime(2023, 11, 8, 17, 20, 34), + }, + { + "imageTags": ["cache"], + "imagePushedAt": datetime(2023, 11, 8, 10, 31, 8), + }, + { + "imageTags": ["commit-57c0a08"], + "imagePushedAt": datetime(2023, 11, 1, 17, 37, 2), + }, + ] + } + ] + + codebase.list("test-application", True) + + mocks.echo_fn.assert_has_calls( + [ + call("The following codebases are available:"), + call("- application (https://github.com/uktrade/example)"), + call( + " - https://github.com/uktrade/example/commit/ee4a82c - published: 2023-11-08 17:55:35" + ), + call( + " - https://github.com/uktrade/example/commit/d269d51 - published: 2023-11-08 17:20:34" + ), + call( + " - https://github.com/uktrade/example/commit/57c0a08 - published: 2023-11-01 17:37:02" + ), + call(""), + ] + ) + + +def is_same_files(compare_directories): + """ + Recursively compare two directories to check if the files are the same or + not. + + Returns True or False. + """ + if ( + compare_directories.diff_files + or compare_directories.left_only + or compare_directories.right_only + ): + for name in compare_directories.diff_files: + print( + "diff_file %s found in %s and %s" + % (name, compare_directories.left, compare_directories.right) + ) + + return False + + for sub_compare_directories in compare_directories.subdirs.values(): + if not is_same_files(sub_compare_directories): + return False + + return True + + +def mock_suprocess_fixture(): + mock_stdout = MagicMock() + mock_stdout.configure_mock(**{"stdout.decode.return_value": '{"A": 3}'}) + return mock_stdout diff --git a/tests/platform_helper/domain/test_database_copy.py b/tests/platform_helper/domain/test_database_copy.py index 9b8ef4456..22bd2d98f 100644 --- a/tests/platform_helper/domain/test_database_copy.py +++ b/tests/platform_helper/domain/test_database_copy.py @@ -6,9 +6,9 @@ from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE from dbt_platform_helper.domain.database_copy import DatabaseCopy +from dbt_platform_helper.exceptions import ApplicationNotFoundError from dbt_platform_helper.exceptions import AWSException from dbt_platform_helper.utils.application import Application -from dbt_platform_helper.utils.application import ApplicationNotFoundError from dbt_platform_helper.utils.aws import Vpc diff --git a/tests/platform_helper/test_command_codebase.py b/tests/platform_helper/test_command_codebase.py index 171ae7cef..137b4c038 100644 --- a/tests/platform_helper/test_command_codebase.py +++ b/tests/platform_helper/test_command_codebase.py @@ -1,23 +1,20 @@ -import filecmp -import json import os -import stat -import subprocess -from datetime import datetime -from pathlib import Path from unittest.mock import MagicMock -from unittest.mock import PropertyMock from unittest.mock import patch -import boto3 -import requests from click.testing import CliRunner -from dbt_platform_helper.utils.application import ApplicationNotFoundError -from tests.platform_helper.conftest import EXPECTED_FILES_DIR - -real_ecr_client = boto3.client("ecr") -real_ssm_client = boto3.client("ssm") +from dbt_platform_helper.commands.codebase import build +from dbt_platform_helper.commands.codebase import deploy +from dbt_platform_helper.commands.codebase import list +from dbt_platform_helper.commands.codebase import prepare as prepare_command +from dbt_platform_helper.exceptions import ApplicationEnvironmentNotFoundError +from dbt_platform_helper.exceptions import ApplicationNotFoundError +from dbt_platform_helper.exceptions import CopilotCodebaseNotFoundError +from dbt_platform_helper.exceptions import ImageNotFoundError +from dbt_platform_helper.exceptions import NoCopilotCodebasesFoundError +from dbt_platform_helper.exceptions import NotInCodeBaseRepositoryError +from dbt_platform_helper.utils.git import CommitNotFoundError def mock_aws_client(get_aws_session_or_abort): @@ -30,169 +27,64 @@ def mock_aws_client(get_aws_session_or_abort): class TestCodebasePrepare: - @patch("dbt_platform_helper.commands.codebase.requests.get") - def test_codebase_prepare_generates_the_expected_files(self, mocked_requests_get, tmp_path): - from dbt_platform_helper.commands.codebase import prepare - - mocked_response_content = """ - builders: - - name: paketobuildpacks/builder-jammy-full - versions: - - version: 0.3.294 - - version: 0.3.288 - - name: paketobuildpacks/builder-jammy-base - versions: - - version: 0.1.234 - - version: 0.5.678 - - name: paketobuildpacks/builder - deprecated: true - versions: - - version: 0.2.443-full - """ - - def mocked_response(): - r = requests.Response() - r.status_code = 200 - type(r).content = PropertyMock(return_value=mocked_response_content.encode("utf-8")) - - return r - - mocked_requests_get.return_value = mocked_response() - - os.chdir(tmp_path) - - subprocess.run(["git", "init"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - subprocess.run( - ["git", "remote", "add", "origin", "git@github.com:uktrade/test-app.git"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - result = CliRunner().invoke(prepare) - - expected_files_dir = Path(EXPECTED_FILES_DIR) / ".copilot" - copilot_dir = Path.cwd() / ".copilot" - - compare_directories = filecmp.dircmp(str(expected_files_dir), str(copilot_dir)) + @patch("dbt_platform_helper.commands.codebase.Codebase") + def test_codebase_prepare_calls_codebase_prepare_method(self, mock_codebase_object): + mock_codebase_object_instance = mock_codebase_object.return_value - for phase in ["build", "install", "post_build", "pre_build"]: - assert f"phases/{phase}.sh" in result.stdout + result = CliRunner().invoke(prepare_command) + mock_codebase_object_instance.prepare.assert_called_once() assert result.exit_code == 0 - assert is_same_files(compare_directories) is True - - def test_codebase_prepare_does_not_generate_files_in_the_deploy_repo(self, tmp_path): - from dbt_platform_helper.commands.codebase import prepare - - os.chdir(tmp_path) - - subprocess.run(["git", "init"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - subprocess.run( - ["git", "remote", "add", "origin", "git@github.com:uktrade/test-app-deploy.git"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - result = CliRunner().invoke(prepare) - assert ( - "You are in the deploy repository; make sure you are in the application codebase repository." - in result.stdout - ) - assert result.exit_code == 1 - - def test_codebase_prepare_does_not_generate_files_in_a_repo_with_a_copilot_directory( - self, tmp_path - ): - from dbt_platform_helper.commands.codebase import prepare - - os.chdir(tmp_path) - Path(tmp_path / "copilot").mkdir() - - subprocess.run(["git", "init"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - subprocess.run( - ["git", "remote", "add", "origin", "git@github.com:uktrade/some-test-app.git"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_aborts_when_not_in_a_codebase_repository(self, mock_click, mock_codebase_object): + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.prepare.side_effect = NotInCodeBaseRepositoryError + os.environ["AWS_PROFILE"] = "foo" - result = CliRunner().invoke(prepare) + result = CliRunner().invoke(prepare_command) - assert ( - "You are in the deploy repository; make sure you are in the application codebase repository." - in result.stdout - ) + expected_message = "You are in the deploy repository; make sure you are in the application codebase repository." + mock_click.assert_called_with(expected_message, fg="red") assert result.exit_code == 1 - def test_codebase_prepare_generates_an_executable_image_build_run_file(self, tmp_path): - from dbt_platform_helper.commands.codebase import prepare - - os.chdir(tmp_path) - - subprocess.run(["git", "init"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - subprocess.run( - ["git", "remote", "add", "origin", "git@github.com:uktrade/another-test-app.git"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - result = CliRunner().invoke(prepare) - - assert result.exit_code == 0 - assert stat.filemode(Path(".copilot/image_build_run.sh").stat().st_mode) == "-rwxr--r--" - class TestCodebaseBuild: - @patch("click.confirm") - @patch("subprocess.run") - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_build_successfully_triggers_a_pipeline_based_build( - self, get_aws_session_or_abort, mock_subprocess_run, mock_click_confirm + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_codebase_build_does_not_trigger_build_without_an_application( + self, mock_click, mock_codebase_object ): - from dbt_platform_helper.commands.codebase import build - - mock_subprocess_run.return_value.stderr = "" - mock_click_confirm.return_value = "y" - client = mock_aws_client(get_aws_session_or_abort) - client.start_build.return_value = { - "build": { - "arn": "arn:aws:codebuild:eu-west-2:111111111111:build/build-project:build-id", - } - } + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.build.side_effect = ApplicationNotFoundError + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke( build, [ "--app", - "test-application", + "not-an-application", "--codebase", "application", "--commit", "ab1c23d", ], ) + expected_message = f"""The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" + mock_click.assert_called_with(expected_message, fg="red") - client.start_build.assert_called_with( - projectName="codebuild-test-application-application", - artifactsOverride={"type": "NO_ARTIFACTS"}, - sourceVersion="ab1c23d", - ) - - assert ( - "Your build has been triggered. Check your build progress in the AWS Console: " - "https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/111111111111/projects/build" - "-project/build/build-project%3Abuild-id" in result.output - ) + assert result.exit_code == 1 - @patch("subprocess.run") - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") def test_codebase_build_aborts_with_a_nonexistent_commit_hash( - self, mock_aws_session, mock_subprocess_run + self, mock_click, mock_codebase_object ): - from dbt_platform_helper.commands.codebase import build - - mock_subprocess_run.return_value.stderr = "malformed" + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.build.side_effect = CommitNotFoundError() + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke( build, @@ -206,84 +98,22 @@ def test_codebase_build_aborts_with_a_nonexistent_commit_hash( ], ) - assert ( - """The commit hash "nonexistent-commit-hash" either does not exist or you need to run `git fetch`.""" - in result.output - ) - - @patch("click.confirm") - @patch("subprocess.run") - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_build_does_not_trigger_build_without_confirmation( - self, get_aws_session_or_abort, mock_subprocess_run, mock_click_confirm - ): - from dbt_platform_helper.commands.codebase import build - - mock_subprocess_run.return_value.stderr = "" - mock_click_confirm.return_value = False - - result = CliRunner().invoke( - build, - [ - "--app", - "test-application", - "--codebase", - "application", - "--commit", - "ab1c23d", - ], - ) - - assert """Your build was not triggered.""" in result.output - - @patch( - "dbt_platform_helper.commands.codebase.load_application", - side_effect=ApplicationNotFoundError, - ) - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_build_does_not_trigger_build_without_an_application( - self, mock_aws_session, load_application - ): - os.environ["AWS_PROFILE"] = "foo" - from dbt_platform_helper.commands.codebase import build - - result = CliRunner().invoke( - build, - [ - "--app", - "not-an-application", - "--codebase", - "application", - "--commit", - "ab1c23d", - ], - ) - - assert ( - """The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" - in result.output + mock_codebase_object_instance.build.assert_called_once_with( + "test-application", "application", "nonexistent-commit-hash" ) + expected_message = f"""The commit hash "nonexistent-commit-hash" either does not exist or you need to run `git fetch`.""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 class TestCodebaseDeploy: - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") + @patch("dbt_platform_helper.commands.codebase.Codebase") def test_codebase_deploy_successfully_triggers_a_pipeline_based_deploy( - self, get_aws_session_or_abort + self, codebase_object_mock ): - from dbt_platform_helper.commands.codebase import deploy - - client = mock_aws_client(get_aws_session_or_abort) + mock_codebase_object_instance = codebase_object_mock.return_value - client.get_parameter.return_value = { - "Parameter": {"Value": json.dumps({"name": "application"})}, - } - client.start_build.return_value = { - "build": { - "arn": "arn:aws:codebuild:eu-west-2:111111111111:build/build-project:build-id", - }, - } - - result = CliRunner().invoke( + CliRunner().invoke( deploy, [ "--app", @@ -298,82 +128,17 @@ def test_codebase_deploy_successfully_triggers_a_pipeline_based_deploy( input="y\n", ) - client.start_build.assert_called_with( - projectName="pipeline-test-application-application-BuildProject", - artifactsOverride={"type": "NO_ARTIFACTS"}, - sourceTypeOverride="NO_SOURCE", - environmentVariablesOverride=[ - {"name": "COPILOT_ENVIRONMENT", "value": "development"}, - {"name": "IMAGE_TAG", "value": "commit-ab1c23d"}, - ], + mock_codebase_object_instance.deploy.assert_called_once_with( + "test-application", "development", "application", "ab1c23d" ) - assert ( - 'You are about to deploy "test-application" for "application" with commit ' - '"ab1c23d" to the "development" environment. Do you want to continue?' in result.output - ) - assert ( - "Your deployment has been triggered. Check your build progress in the AWS Console: " - "https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/111111111111/projects/build" - "-project/build/build-project%3Abuild-id" in result.output - ) - - @patch("subprocess.run") - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_deploy_aborts_with_a_nonexistent_image_repository( - self, get_aws_session_or_abort, mock_subprocess_run + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_codebase_deploy_aborts_with_a_nonexistent_image_repository_or_image_tag( + self, mock_click, codebase_object_mock ): - from dbt_platform_helper.commands.codebase import deploy - - client = mock_aws_client(get_aws_session_or_abort) - - client.get_parameter.return_value = { - "Parameter": {"Value": json.dumps({"name": "application"})}, - } - client.exceptions.ImageNotFoundException = real_ecr_client.exceptions.ImageNotFoundException - client.exceptions.RepositoryNotFoundException = ( - real_ecr_client.exceptions.RepositoryNotFoundException - ) - client.describe_images.side_effect = real_ecr_client.exceptions.RepositoryNotFoundException( - {}, "" - ) - - result = CliRunner().invoke( - deploy, - [ - "--app", - "test-application", - "--env", - "development", - "--codebase", - "application", - "--commit", - "nonexistent-commit-hash", - ], - ) - - assert 'The ECR Repository for codebase "application" does not exist.' in result.output - - @patch("subprocess.run") - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_deploy_aborts_with_a_nonexistent_image_tag( - self, get_aws_session_or_abort, mock_subprocess_run - ): - from dbt_platform_helper.commands.codebase import deploy - - client = mock_aws_client(get_aws_session_or_abort) - - client.get_parameter.return_value = { - "Parameter": {"Value": json.dumps({"name": "application"})}, - } - client.exceptions.ImageNotFoundException = real_ecr_client.exceptions.ImageNotFoundException - client.exceptions.RepositoryNotFoundException = ( - real_ecr_client.exceptions.RepositoryNotFoundException - ) - client.describe_images.side_effect = real_ecr_client.exceptions.ImageNotFoundException( - {}, "" - ) - + mock_codebase_object_instance = codebase_object_mock.return_value + mock_codebase_object_instance.deploy.side_effect = ImageNotFoundError result = CliRunner().invoke( deploy, [ @@ -388,67 +153,59 @@ def test_codebase_deploy_aborts_with_a_nonexistent_image_tag( ], ) - assert ( - 'The commit hash "nonexistent-commit-hash" has not been built into an image, try the ' - "`platform-helper codebase build` command first." in result.output + mock_codebase_object_instance.deploy.assert_called_once_with( + "test-application", "development", "application", "nonexistent-commit-hash" ) + expected_message = f"""The commit hash "nonexistent-commit-hash" has not been built into an image, try the `platform-helper codebase build` command first.""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_deploy_does_not_trigger_build_without_confirmation( - self, get_aws_session_or_abort + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_codebase_deploy_does_not_trigger_build_without_an_application( + self, mock_click, mock_codebase_object ): - from dbt_platform_helper.commands.codebase import deploy - - client = mock_aws_client(get_aws_session_or_abort) - client.get_parameter.return_value = { - "Parameter": {"Value": json.dumps({"name": "application"})}, - } - client.exceptions.ImageNotFoundException = real_ecr_client.exceptions.ImageNotFoundException - client.exceptions.RepositoryNotFoundException = ( - real_ecr_client.exceptions.RepositoryNotFoundException - ) - client.exceptions.ParameterNotFound = real_ssm_client.exceptions.ParameterNotFound + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.deploy.side_effect = ApplicationNotFoundError + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke( deploy, [ "--app", - "test-application", + "not-an-application", "--env", - "development", + "dev", "--codebase", "application", "--commit", "ab1c23d", ], - input="n\n", ) - assert ( - 'You are about to deploy "test-application" for "application" with commit ' - '"ab1c23d" to the "development" environment. Do you want to continue?' in result.output + mock_codebase_object_instance.deploy.assert_called_once_with( + "not-an-application", "dev", "application", "ab1c23d" ) - assert """Your deployment was not triggered.""" in result.output + expected_message = f"""The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 - @patch( - "dbt_platform_helper.commands.codebase.load_application", - side_effect=ApplicationNotFoundError, - ) - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_deploy_does_not_trigger_build_without_an_application( - self, get_aws_session_or_abort, aws_credentials + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_codebase_deploy_does_not_trigger_build_with_missing_environment( + self, mock_click, mock_codebase_object ): - from dbt_platform_helper.commands.codebase import deploy - + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.deploy.side_effect = ApplicationEnvironmentNotFoundError os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke( deploy, [ "--app", - "not-an-application", + "test-application", "--env", - "dev", + "not-an-environment", "--codebase", "application", "--commit", @@ -456,17 +213,21 @@ def test_codebase_deploy_does_not_trigger_build_without_an_application( ], ) - assert ( - """The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" - in result.output + mock_codebase_object_instance.deploy.assert_called_once_with( + "test-application", "not-an-environment", "application", "ab1c23d" ) + expected_message = f"""The environment "not-an-environment" either does not exist or has not been deployed.""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 - @patch("dbt_platform_helper.utils.application.get_aws_session_or_abort", return_value=boto3) - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_codebase_deploy_does_not_trigger_build_with_missing_environment( - self, get_aws_session_or_abort, aws_credentials, mock_application + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_codebase_deploy_does_not_trigger_build_with_missing_codebase( + self, mock_click, mock_codebase_object ): - from dbt_platform_helper.commands.codebase import deploy + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.deploy.side_effect = CopilotCodebaseNotFoundError + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke( deploy, @@ -474,213 +235,61 @@ def test_codebase_deploy_does_not_trigger_build_with_missing_environment( "--app", "test-application", "--env", - "not-an-environment", + "test-environment", "--codebase", - "application", + "not-a-codebase", "--commit", "ab1c23d", ], ) - assert ( - 'The environment "not-an-environment" either does not exist or has not been deployed.' - in result.output + mock_codebase_object_instance.deploy.assert_called_once_with( + "test-application", "test-environment", "not-a-codebase", "ab1c23d" + ) + expected_message = ( + f"""The codebase "not-a-codebase" either does not exist or has not been deployed.""" ) + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 class TestCodebaseList: - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_lists_codebases_successfully(self, get_aws_session_or_abort): - client = mock_aws_client(get_aws_session_or_abort) - client.get_parameters_by_path.return_value = { - "Parameters": [ - {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} - ], - } - from dbt_platform_helper.commands.codebase import list - - result = CliRunner().invoke(list, ["--app", "test-application"]) - - assert "The following codebases are available:" in result.output - assert "- application (https://github.com/uktrade/example)" in result.output - - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_lists_codebases_with_images_successfully(self, get_aws_session_or_abort): - client = mock_aws_client(get_aws_session_or_abort) - client.get_parameters_by_path.return_value = { - "Parameters": [ - {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} - ], - } - client.get_paginator.return_value.paginate.return_value = [ - { - "imageDetails": [ - { - "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-ee4a82c"], - "imagePushedAt": datetime(2023, 11, 8, 17, 55, 35), - }, - { - "imageTags": ["branch-main", "commit-d269d51"], - "imagePushedAt": datetime(2023, 11, 8, 17, 20, 34), - }, - { - "imageTags": ["cache"], - "imagePushedAt": datetime(2023, 11, 8, 10, 31, 8), - }, - { - "imageTags": ["commit-57c0a08"], - "imagePushedAt": datetime(2023, 11, 1, 17, 37, 2), - }, - ] - } - ] - from dbt_platform_helper.commands.codebase import list + @patch("dbt_platform_helper.commands.codebase.Codebase") + def test_lists_codebases_successfully(self, mock_codebase_object): + mock_codebase_object_instance = mock_codebase_object.return_value + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke(list, ["--app", "test-application", "--with-images"]) - assert "The following codebases are available:" in result.output - assert "- application (https://github.com/uktrade/example)" in result.output - assert ( - "- https://github.com/uktrade/example/commit/ee4a82c - published: 2023-11-08 17:55:35" - in result.output - ) - assert ( - "- https://github.com/uktrade/example/commit/d269d51 - published: 2023-11-08 17:20:34" - in result.output - ) - assert ( - "- https://github.com/uktrade/example/commit/57c0a08 - published: 2023-11-01 17:37:02" - in result.output - ) - - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_lists_codebases_with_multiple_pages_of_images(self, get_aws_session_or_abort): - client = mock_aws_client(get_aws_session_or_abort) - client.get_parameters_by_path.return_value = { - "Parameters": [ - {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} - ], - } - client.get_paginator.return_value.paginate.return_value = [ - { - "imageDetails": [ - { - "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-10"], - "imagePushedAt": datetime(2023, 11, 10, 00, 00, 00), - }, - { - "imageTags": ["branch-main", "commit-9"], - "imagePushedAt": datetime(2023, 11, 9, 00, 00, 00), - }, - ] - }, - { - "imageDetails": [ - { - "imageTags": ["commit-8"], - "imagePushedAt": datetime(2023, 11, 8, 00, 00, 00), - }, - { - "imageTags": ["commit-7"], - "imagePushedAt": datetime(2023, 11, 7, 00, 00, 00), - }, - ] - }, - ] - from dbt_platform_helper.commands.codebase import list - - result = CliRunner().invoke(list, ["--app", "test-application", "--with-images"]) - assert ( - "- https://github.com/uktrade/example/commit/10 - published: 2023-11-10 00:00:00" - in result.output - ) - assert ( - "- https://github.com/uktrade/example/commit/9 - published: 2023-11-09 00:00:00" - in result.output - ) - assert ( - "- https://github.com/uktrade/example/commit/8 - published: 2023-11-08 00:00:00" - in result.output - ) - assert ( - "- https://github.com/uktrade/example/commit/7 - published: 2023-11-07 00:00:00" - in result.output - ) + mock_codebase_object_instance.list.assert_called_once_with("test-application", True) + assert result.exit_code == 0 - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_lists_codebases_with_disordered_images_in_chronological_order( - self, get_aws_session_or_abort - ): - client = mock_aws_client(get_aws_session_or_abort) - client.get_parameters_by_path.return_value = { - "Parameters": [ - {"Value": json.dumps({"name": "application", "repository": "uktrade/example"})} - ], - } - client.get_paginator.return_value.paginate.return_value = [ - { - "imageDetails": [ - { - "imageTags": ["latest", "tag-latest", "tag-1.0", "commit-4"], - "imagePushedAt": datetime(2023, 11, 4, 00, 00, 00), - }, - { - "imageTags": ["branch-main", "commit-2"], - "imagePushedAt": datetime(2023, 11, 2, 00, 00, 00), - }, - ] - }, - { - "imageDetails": [ - { - "imageTags": ["commit-1"], - "imagePushedAt": datetime(2023, 11, 1, 00, 00, 00), - }, - { - "imageTags": ["commit-3"], - "imagePushedAt": datetime(2023, 11, 3, 00, 00, 00), - }, - ] - }, - ] - from dbt_platform_helper.commands.codebase import list + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_list_aborts_when_application_has_no_codebases(self, mock_click, mock_codebase_object): + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.list.side_effect = NoCopilotCodebasesFoundError + os.environ["AWS_PROFILE"] = "foo" result = CliRunner().invoke(list, ["--app", "test-application", "--with-images"]) - assert ( - result.output.index("commit/4") - < result.output.index("commit/3") - < result.output.index("commit/2") - < result.output.index("commit/1") - ) - - @patch( - "dbt_platform_helper.commands.codebase.load_application", - side_effect=ApplicationNotFoundError, - ) - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_aborts_when_application_does_not_exist(self, mock_aws_session, load_application): - from dbt_platform_helper.commands.codebase import list + expected_message = f"""No codebases found for application "test-application""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 + @patch("dbt_platform_helper.commands.codebase.Codebase") + @patch("click.secho") + def test_aborts_when_application_does_not_exist(self, mock_click, mock_codebase_object): + mock_codebase_object_instance = mock_codebase_object.return_value + mock_codebase_object_instance.list.side_effect = ApplicationNotFoundError os.environ["AWS_PROFILE"] = "foo" - result = CliRunner().invoke(list, ["--app", "not-an-application"]) - - assert ( - """The account "foo" does not contain the application "not-an-application"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" - in result.output - ) - - @patch("dbt_platform_helper.commands.codebase.get_aws_session_or_abort") - def test_aborts_when_application_has_no_codebases(self, get_aws_session_or_abort): - from dbt_platform_helper.commands.codebase import list - client = mock_aws_client(get_aws_session_or_abort) - - client.get_parameters_by_path.return_value = {"Parameters": []} - - result = CliRunner().invoke(list, ["--app", "test-application"]) + result = CliRunner().invoke(list, ["--app", "test-application", "--with-images"]) - assert 'No codebases found for application "test-application"' in result.output + app = "test-application" + expected_message = f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{app}"; ensure you have set the environment variable "AWS_PROFILE" correctly.""" + mock_click.assert_called_with(expected_message, fg="red") + assert result.exit_code == 1 def is_same_files(compare_directories): diff --git a/tests/platform_helper/utils/test_application.py b/tests/platform_helper/utils/test_application.py index a20395efd..a3bce6d5a 100644 --- a/tests/platform_helper/utils/test_application.py +++ b/tests/platform_helper/utils/test_application.py @@ -7,8 +7,8 @@ import boto3 from moto import mock_aws +from dbt_platform_helper.exceptions import ApplicationNotFoundError from dbt_platform_helper.utils.application import Application -from dbt_platform_helper.utils.application import ApplicationNotFoundError from dbt_platform_helper.utils.application import Environment from dbt_platform_helper.utils.application import get_application_name from dbt_platform_helper.utils.application import load_application diff --git a/tests/platform_helper/utils/test_aws.py b/tests/platform_helper/utils/test_aws.py index 1a96af455..b60fe18ba 100644 --- a/tests/platform_helper/utils/test_aws.py +++ b/tests/platform_helper/utils/test_aws.py @@ -25,6 +25,8 @@ from dbt_platform_helper.utils.aws import get_profile_name_from_account_id from dbt_platform_helper.utils.aws import get_public_repository_arn from dbt_platform_helper.utils.aws import get_ssm_secrets +from dbt_platform_helper.utils.aws import get_supported_opensearch_versions +from dbt_platform_helper.utils.aws import get_supported_redis_versions from dbt_platform_helper.utils.aws import get_vpc_info_by_name from dbt_platform_helper.utils.aws import set_ssm_param from tests.platform_helper.conftest import mock_aws_client @@ -621,6 +623,60 @@ def test_update_postgres_parameter_with_master_secret(): } +@patch("dbt_platform_helper.utils.aws.cache_refresh_required", return_value=True) +@patch("dbt_platform_helper.utils.aws.get_aws_session_or_abort") +@patch("dbt_platform_helper.utils.aws.write_to_cache") +def test_get_supported_redis_versions_when_cache_refresh_required( + mock_cache_refresh, mock_get_aws_session_or_abort, mock_write_to_cache +): + + client = mock_aws_client(mock_get_aws_session_or_abort) + client.describe_cache_engine_versions.return_value = { + "CacheEngineVersions": [ + { + "Engine": "redis", + "EngineVersion": "4.0.10", + "CacheParameterGroupFamily": "redis4.0", + "CacheEngineDescription": "Redis", + "CacheEngineVersionDescription": "redis version 4.0.10", + }, + { + "Engine": "redis", + "EngineVersion": "5.0.6", + "CacheParameterGroupFamily": "redis5.0", + "CacheEngineDescription": "Redis", + "CacheEngineVersionDescription": "redis version 5.0.6", + }, + ] + } + + supported_redis_versions_response = get_supported_redis_versions() + assert supported_redis_versions_response == ["4.0.10", "5.0.6"] + + +@patch("dbt_platform_helper.utils.aws.cache_refresh_required", return_value=True) +@patch("dbt_platform_helper.utils.aws.get_aws_session_or_abort") +@patch("dbt_platform_helper.utils.aws.write_to_cache") +def test_get_supported_opensearch_versions_when_cache_refresh_required( + mock_cache_refresh, mock_get_aws_session_or_abort, mock_write_to_cache +): + + client = mock_aws_client(mock_get_aws_session_or_abort) + client.list_versions.return_value = { + "Versions": [ + "OpenSearch_2.15", + "OpenSearch_2.13", + "OpenSearch_2.11", + "OpenSearch_2.9", + "Elasticsearch_7.10", + "Elasticsearch_7.9", + ] + } + + supported_opensearch_versions_response = get_supported_opensearch_versions() + assert supported_opensearch_versions_response == ["2.15", "2.13", "2.11", "2.9"] + + @mock_aws def test_get_connection_string(): db_identifier = f"my_app-my_env-my_postgres" @@ -649,7 +705,6 @@ def test_get_connection_string(): mock_connection_data.assert_called_once_with( session, f"/copilot/my_app/my_env/secrets/MY_POSTGRES_READ_ONLY_USER", master_secret_arn ) - # Ignoring this does not work, see https://github.com/trufflesecurity/trufflehog/issues/3602 assert ( connection_string == "postgres://master_user:master_password@hostname:1234/main" # trufflehog:ignore diff --git a/tests/platform_helper/utils/test_files.py b/tests/platform_helper/utils/test_files.py index b4e6083d8..0157a02f0 100644 --- a/tests/platform_helper/utils/test_files.py +++ b/tests/platform_helper/utils/test_files.py @@ -1,9 +1,13 @@ import os +from datetime import datetime +from datetime import timedelta from pathlib import Path +from unittest.mock import patch import pytest from dbt_platform_helper.utils.files import apply_environment_defaults +from dbt_platform_helper.utils.files import cache_refresh_required from dbt_platform_helper.utils.files import generate_override_files from dbt_platform_helper.utils.files import generate_override_files_from_template from dbt_platform_helper.utils.files import mkfile @@ -230,3 +234,38 @@ def test_apply_defaults_with_no_defaults(): "three": {"a": "aaa", "versions": {}}, }, } + + +@patch("dbt_platform_helper.utils.files.os.path.exists", return_value=True) +@patch("dbt_platform_helper.utils.files.read_file_as_yaml") +def test_cache_refresh_required_is_true_when_cached_datetime_greater_than_one_day( + mock_read_yaml, mock_path_exists +): + + read_yaml_return_value = { + "redis": { + # Some timestamp which is > than 1 day. i.e. enough to trigger a cache refresh + "date-retrieved": "09-02-02 10:35:48" + } + } + mock_read_yaml.return_value = read_yaml_return_value + + assert cache_refresh_required("redis") + + +@patch("dbt_platform_helper.utils.files.os.path.exists", return_value=True) +@patch("dbt_platform_helper.utils.files.read_file_as_yaml") +def test_cache_refresh_required_is_false_when_cached_datetime_less_than_one_day( + mock_read_yaml, mock_path_exists +): + + today = datetime.now() + # Time range is still < 1 day so should not require refresh + middle_of_today = today - timedelta(hours=12) + + read_yaml_return_value = { + "redis": {"date-retrieved": middle_of_today.strftime("%d-%m-%y %H:%M:%S")} + } + mock_read_yaml.return_value = read_yaml_return_value + + assert not cache_refresh_required("redis") diff --git a/tests/platform_helper/utils/test_validation.py b/tests/platform_helper/utils/test_validation.py index 8b39dac45..c99dd1b41 100644 --- a/tests/platform_helper/utils/test_validation.py +++ b/tests/platform_helper/utils/test_validation.py @@ -20,6 +20,7 @@ from dbt_platform_helper.utils.validation import validate_platform_config from dbt_platform_helper.utils.validation import validate_s3_bucket_name from dbt_platform_helper.utils.validation import validate_string +from dbt_platform_helper.utils.validation import _validate_extension_supported_versions from tests.platform_helper.conftest import FIXTURES_DIR from tests.platform_helper.conftest import UTILS_FIXTURES_DIR @@ -146,7 +147,6 @@ def test_validate_addons_success(addons_file): "redis_addons_bad_data.yml", { "my-redis-bad-key": r"Wrong key 'bad_key' in", - "my-redis-bad-engine-size": r"environments.*default.*engine.*'6.2' does not match 'a-big-engine'", "my-redis-bad-plan": r"environments.*default.*plan.*does not match 'enormous'", "my-redis-too-many-replicas": r"environments.*default.*replicas.*should be an integer between 0 and 5", "my-redis-bad-deletion-policy": r"environments.*default.*deletion_policy.*does not match 'Never'", @@ -163,7 +163,6 @@ def test_validate_addons_success(addons_file): "my-opensearch-environments-should-be-list": r"environments.*False should be instance of 'dict'", "my-opensearch-bad-env-param": r"environments.*Wrong key 'opensearch_plan'", "my-opensearch-bad-plan": r"environments.*dev.*plan.*does not match 'largish'", - "my-opensearch-bad-engine-size": r"environments.*dev.*engine.*does not match 7.3", "my-opensearch-no-plan": r"Missing key: 'plan'", "my-opensearch-volume-size-too-small": r"environments.*dev.*volume_size.*should be an integer greater than 10", "my-opensearch-invalid-size-for-small": r"environments.*dev.*volume_size.*should be an integer between 10 and [0-9]{2,4}.* for plan.*", @@ -232,7 +231,16 @@ def test_validate_addons_success(addons_file): ), ], ) -def test_validate_addons_failure(addons_file, exp_error): +@patch("dbt_platform_helper.utils.validation.get_supported_redis_versions", return_value=["6.2"]) +@patch( + "dbt_platform_helper.utils.validation.get_supported_opensearch_versions", return_value=["1.3"] +) +def test_validate_addons_failure( + mock_get_redis_versions, + mock_get_opensearch_versions, + addons_file, + exp_error, +): error_map = validate_addons(load_addons(addons_file)) for entry, error in exp_error.items(): assert entry in error_map @@ -258,6 +266,7 @@ def test_validate_addons_invalid_env_name_errors(): def test_validate_addons_unsupported_addon(): error_map = validate_addons(load_addons("unsupported_addon.yml")) + for entry, error in error_map.items(): assert "Unsupported addon type 'unsupported_addon' in addon 'my-unsupported-addon'" == error @@ -914,3 +923,60 @@ def test_validate_database_copy_multi_postgres_failures(capfd): f"Copying to a prod environment is not supported: database_copy 'to' cannot be 'prod' in extension 'our-other-postgres'." in console_message ) + + +@patch("dbt_platform_helper.utils.validation.get_supported_redis_versions", return_value=["7.1"]) +def test_validate_extensions_supported_versions_successful_with_supported_version( + mock_supported_versions, capsys +): + + config = { + "application": "test-app", + "environments": {"dev": {}, "test": {}, "prod": {}}, + "extensions": { + "connors-redis": {"type": "redis", "environments": {"*": {"engine": "7.1"}}} + }, + } + + _validate_extension_supported_versions( + config=config, + extension_type="redis", + version_key="engine", + get_supported_versions_fn=mock_supported_versions, + ) + + # Nothing should be logged if the version is valid. + captured = capsys.readouterr() + assert captured.out == "" + assert captured.err == "" + + +@patch("dbt_platform_helper.utils.validation.get_supported_redis_versions", return_value=["7.1"]) +def test_validate_extensions_supported_versions_fails_with_unsupported_version( + mock_supported_versions, capsys +): + + config = { + "application": "test-app", + "environments": {"dev": {}, "test": {}, "prod": {}}, + "extensions": { + "connors-redis": { + "type": "redis", + "environments": {"*": {"engine": "some-engine-which-probably-doesnt-exist"}}, + } + }, + } + + _validate_extension_supported_versions( + config=config, + extension_type="redis", + version_key="engine", + get_supported_versions_fn=mock_supported_versions, + ) + + captured = capsys.readouterr() + assert ( + "redis version for environment * is not in the list of supported redis versions" + in captured.out + ) + assert captured.err == ""