From 698824d63893c21048c1b43a3c5027c6cb0513b1 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Thu, 18 Jun 2026 23:16:33 -0500 Subject: [PATCH 1/8] chore: rewrite project structure --- .github/workflows/deploy.yml | 13 +- .github/workflows/tests.yml | 27 - .gitignore | 1 + README.md | 2 +- app.py | 137 + cdk.json | 10 +- cdk/MaapEoapiCommon.ts | 61 - cdk/PatchManager.ts | 91 - cdk/Vpc.ts | 56 - cdk/__init__.py | 0 cdk/config.py | 93 + cdk/constructs/DpsStacItemGenerator/index.ts | 246 - cdk/constructs/__init__.py | 0 cdk/constructs/dps_stac_item_generator.py | 172 + cdk/maap_eoapi_common.py | 54 + cdk/patch_manager.py | 100 + cdk/pgstac_infra.py | 736 +++ cdk/vpc.py | 65 + jest.config.js | 8 - package-lock.json | 5450 ------------------ package.json | 31 - pyproject.toml | 11 + test/README.md | 38 - tests/README.md | 46 + tests/requirements.txt | 5 - tests/test_pgstac_infra.py | 217 + tsconfig.json | 30 - uv.lock | 778 ++- 28 files changed, 2420 insertions(+), 6058 deletions(-) create mode 100644 app.py delete mode 100644 cdk/MaapEoapiCommon.ts delete mode 100644 cdk/PatchManager.ts delete mode 100644 cdk/Vpc.ts create mode 100644 cdk/__init__.py create mode 100644 cdk/config.py delete mode 100644 cdk/constructs/DpsStacItemGenerator/index.ts create mode 100644 cdk/constructs/__init__.py create mode 100644 cdk/constructs/dps_stac_item_generator.py create mode 100644 cdk/maap_eoapi_common.py create mode 100644 cdk/patch_manager.py create mode 100644 cdk/pgstac_infra.py create mode 100644 cdk/vpc.py delete mode 100644 jest.config.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 test/README.md create mode 100644 tests/README.md delete mode 100644 tests/requirements.txt create mode 100644 tests/test_pgstac_infra.py delete mode 100644 tsconfig.json diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8c6363a..5c29e1c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -62,14 +62,13 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Set up uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - node-version: 20 - cache: 'npm' + enable-cache: true - name: Install dependencies - run: npm ci + run: uv sync --locked - name: Assume Github OIDC role uses: aws-actions/configure-aws-credentials@99214aa6889fcddfa57764031d71add364327e59 # v6.1.3 @@ -103,8 +102,8 @@ jobs: echo "AWS_DEFAULT_REGION=us-west-2" >> $GITHUB_ENV - name: Run CDK synth - run: npm run cdk -- synth + run: uv run npx cdk synth "*" - name: Run CDK deploy if: github.event_name == 'workflow_dispatch' - run: npm run cdk -- deploy --all --require-approval never + run: uv run npx cdk deploy "*" --require-approval never diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 01deae2..656fe58 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,28 +10,6 @@ on: workflow_dispatch: jobs: - node-tests: - name: node-tests - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "20" - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Run tests - run: npm test - python-runtime-tests: name: pytest (${{ matrix.runtime.name }}) runs-on: ubuntu-latest @@ -54,11 +32,6 @@ jobs: with: persist-credentials: false - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.12" - - name: Set up uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: diff --git a/.gitignore b/.gitignore index 248242c..ec45d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules .pyc __pycache__ +.pytest_cache .venv .env .envrc diff --git a/README.md b/README.md index db695ca..52d4ced 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Overview -This repository contains the AWS CDK code (written in typescript) used to deploy the MAAP project eoapi infrastructure. It is based on the [eoapi-template example](https://github.com/developmentseed/eoapi-template). For the MAAP use case, we use a subset of the eoapi CDK constructs to define a database, an ingestion API, a STAC API, a raster API (i.e a tiling API) and a pgbouncer instance to manage connections to the database. Here, we deploy all these components into a custom VPC. +This repository contains the AWS CDK code (written in Python) used to deploy the MAAP project eoapi infrastructure. It is based on the [eoapi-template example](https://github.com/developmentseed/eoapi-template). For the MAAP use case, we use a subset of the eoapi CDK constructs to define a database, an ingestion API, a STAC API, a raster API (i.e a tiling API) and a pgbouncer instance to manage connections to the database. Here, we deploy all these components into a custom VPC. ## Automated Deployment diff --git a/app.py b/app.py new file mode 100644 index 0000000..d2bb8c9 --- /dev/null +++ b/app.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import aws_cdk as cdk + +from cdk.config import Config +from cdk.maap_eoapi_common import MaapEoapiCommon +from cdk.patch_manager import PatchManagerStack +from cdk.pgstac_infra import ( + DpsStacItemGenConfig, + IngestorConfig, + PgStacDbConfig, + PgStacInfra, + StacApiConfig, + StacBrowserConfig, + TitilerPgstacConfig, +) +from cdk.vpc import VpcStack + +config = Config() + +app = cdk.App() + +vpc_stack = VpcStack( + app, + config.build_stack_name("vpc"), + termination_protection=False, + tags=config.tags, + nat_gateway_count=None if config.stage == "prod" else 1, +) + +# Create common resources to be shared by pgSTAC and userSTAC stacks +common = MaapEoapiCommon( + app, + config.build_stack_name("common"), + tags=config.tags, + stage=config.stage, + termination_protection=False, +) + +core_infrastructure = PgStacInfra( + app, + config.build_stack_name("pgSTAC"), + vpc=vpc_stack.vpc, + tags=config.tags, + stage=config.stage, + type="public", + version=config.version, + certificate_arn=config.certificate_arn, + web_acl_arn=config.web_acl_arn, + logging_bucket_arn=common.logging_bucket.bucket_arn, + pgstac_db_config=PgStacDbConfig( + instance_type=config.db_instance_type, + pgstac_version=config.pgstac_version, + allocated_storage=config.db_allocated_storage, + subnet_public=False, + ), + stac_api_config=StacApiConfig( + custom_domain_name=config.stac_api_custom_domain_name, + integration_api_arn=config.stac_api_integration_api_arn, + ), + titiler_pgstac_config=TitilerPgstacConfig( + mosaic_host=config.mosaic_host, + buckets_path="./titiler_buckets.yaml", + custom_domain_name=config.titiler_pg_stac_api_custom_domain_name, + data_access_role_arn=config.titiler_data_access_role_arn, + ), + stac_browser_config=StacBrowserConfig( + repo_tag=config.stac_browser_repo_tag, + custom_domain_name=config.stac_browser_custom_domain_name, + certificate_arn=config.stac_browser_certificate_arn, + ), + ingestor_config=IngestorConfig( + jwks_url=config.jwks_url, + data_access_role_arn=config.ingestor_data_access_role_arn, + domain_name=config.ingestor_domain_name, + user_data_path="./userdata.yaml", + ), + add_stactools_item_generator=True, + termination_protection=False, +) + +user_infrastructure = PgStacInfra( + app, + config.build_stack_name("userSTAC"), + vpc=vpc_stack.vpc, + tags=config.tags, + stage=config.stage, + type="internal", + version=config.version, + certificate_arn=config.certificate_arn, + web_acl_arn=config.web_acl_arn, + logging_bucket_arn=common.logging_bucket.bucket_arn, + pgstac_db_config=PgStacDbConfig( + instance_type=config.db_instance_type, + pgstac_version=config.pgstac_version, + allocated_storage=config.db_allocated_storage, + subnet_public=False, + ), + stac_api_config=StacApiConfig( + custom_domain_name=config.user_stac_stac_api_custom_domain_name, + transactions=( + config.user_stac_collection_transactions # type: ignore[arg-type] + ), + ), + titiler_pgstac_config=TitilerPgstacConfig( + mosaic_host=config.mosaic_host, + buckets_path="./titiler_buckets.yaml", + custom_domain_name=config.user_stac_titiler_pgstac_api_custom_domain_name, + data_access_role_arn=config.titiler_data_access_role_arn, + ), + add_stactools_item_generator=False, + **( + { + "dps_stac_item_gen_config": DpsStacItemGenConfig( + item_gen_role_arn=config.user_stac_item_gen_role_arn, + inbound_topic_arns=config.user_stac_inbound_topic_arns, + user_stac_collection_id_registry=config.user_stac_collection_id_registry, + ) + } + if config.user_stac_item_gen_role_arn + else {} + ), + termination_protection=False, +) + +patch_manager = PatchManagerStack( + app, + config.build_stack_name("patch-manager"), + pgbouncer_param_names=[ + f"/maap-eoapi/{config.stage}/public/pgbouncer-instance-id", + f"/maap-eoapi/{config.stage}/internal/pgbouncer-instance-id", + ], + termination_protection=False, +) +patch_manager.add_dependency(core_infrastructure) +patch_manager.add_dependency(user_infrastructure) + +app.synth() diff --git a/cdk.json b/cdk.json index b59a563..5f4439d 100644 --- a/cdk.json +++ b/cdk.json @@ -1,5 +1,5 @@ { - "app": "npx ts-node --prefer-ts-exts cdk/app.ts", + "app": "python app.py", "watch": { "include": ["**"], "exclude": [ @@ -7,11 +7,11 @@ "cdk*.json", "**/*.d.ts", "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", "node_modules", - "test" + "tests", + "**/__pycache__", + "**/.venv", + "uv.lock" ] }, "context": { diff --git a/cdk/MaapEoapiCommon.ts b/cdk/MaapEoapiCommon.ts deleted file mode 100644 index 3ad5aab..0000000 --- a/cdk/MaapEoapiCommon.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { aws_s3 as s3 } from "aws-cdk-lib"; -import { Duration, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; -import { Construct } from "constructs"; - -export interface MaapEoapiCommonProps extends StackProps { - /** - * Stage for this stack. Used for naming resources. - */ - stage: string; -} - -/** - * MaapEoapiCommon Stack - * - * This stack contains shared resources that are used by both the pgSTAC and userSTAC stacks. - * Any resources that need to be accessed or referenced by multiple stacks should be placed here - * to avoid circular dependencies and ensure proper resource sharing. - * - * Examples of shared resources include: - * - Logging buckets for centralized log collection - * - Monitoring resources - * - IAM roles or policies that are used across stacks - * - * This pattern ensures clean separation of concerns while enabling resource reuse - * across the MAAP eoAPI infrastructure. - */ -export class MaapEoapiCommon extends Stack { - /** - * S3 bucket for centralized logging across all MAAP eoAPI stacks. - * This bucket is used by both pgSTAC and userSTAC stacks for storing access logs - * and other operational logs. - */ - public readonly loggingBucket: s3.Bucket; - - /** - * Constructs the MaapEoapiCommon stack with shared resources. - * - * @param scope - The scope in which to define this construct - * @param id - The scoped construct ID. Must be unique amongst siblings - * @param props - Stack properties including the deployment stage - */ - constructor(scope: Construct, id: string, props: MaapEoapiCommonProps) { - super(scope, id, props); - - const { stage } = props; - - this.loggingBucket = new s3.Bucket(this, "maapLoggingBucket", { - accessControl: s3.BucketAccessControl.LOG_DELIVERY_WRITE, - removalPolicy: RemovalPolicy.RETAIN, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - bucketName: `maap-service-logging-${stage}`, - enforceSSL: true, - lifecycleRules: [ - { - enabled: true, - expiration: Duration.days(395), - }, - ], - }); - } -} diff --git a/cdk/PatchManager.ts b/cdk/PatchManager.ts deleted file mode 100644 index 0887392..0000000 --- a/cdk/PatchManager.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Stack, StackProps } from 'aws-cdk-lib'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import { Construct } from 'constructs'; - -export class PatchManagerStack extends Stack { - constructor(scope: Construct, id: string, props: Props) { - super(scope, id, props); - - // IAM role used by the maintenance window - const maintenanceRole = new iam.Role(this, 'MaintenanceWindowRole', { - assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), - }); - - maintenanceRole.addToPolicy( - new iam.PolicyStatement({ - actions: [ - 'ssm:SendCommand', - 'ssm:ListCommands', - 'ssm:ListCommandInvocations', - ], - resources: ['*'], - }), - ); - - // Maintenance Window - const maintenanceWindow = new ssm.CfnMaintenanceWindow( - this, - 'PatchMaintenanceWindow', - { - name: 'patch-maintenance-window', - description: 'Weekly patching using AWS default patch baseline', - schedule: 'cron(0 7 ? * WED *)', // Wednesdays 07:00 UTC - duration: 3, - cutoff: 1, - allowUnassociatedTargets: false, - }, - ); - - const instanceIds = props.pgbouncerParamNames.map((paramName) => - ssm.StringParameter.valueForStringParameter(this, paramName) - ); - - // Target EC2 instances by Name tag - const target = new ssm.CfnMaintenanceWindowTarget( - this, - 'PatchTarget', - { - windowId: maintenanceWindow.ref, - resourceType: 'INSTANCE', - targets: [ - { - key: 'InstanceIds', - values: instanceIds, - }, - ], - }, - ); - - // Patch task (Install) - new ssm.CfnMaintenanceWindowTask(this, 'PatchInstallTask', { - windowId: maintenanceWindow.ref, - taskArn: 'AWS-RunPatchBaseline', - taskType: 'RUN_COMMAND', - priority: 1, - maxConcurrency: '2', - maxErrors: '1', - serviceRoleArn: maintenanceRole.roleArn, - targets: [ - { - key: 'WindowTargetIds', - values: [target.ref], - }, - ], - taskInvocationParameters: { - maintenanceWindowRunCommandParameters: { - parameters: { - Operation: ['Install'], - }, - }, - }, - }); - } -} - -export interface Props extends StackProps { - /** - * SSM parameter names storing the PgBouncer EC2 instance IDs to target for patching. - */ - pgbouncerParamNames: string[]; -} diff --git a/cdk/Vpc.ts b/cdk/Vpc.ts deleted file mode 100644 index 0d960e6..0000000 --- a/cdk/Vpc.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Stack, StackProps, aws_ec2 as ec2 } from "aws-cdk-lib"; -import { Construct } from "constructs"; - -export class Vpc extends Stack { - vpc: ec2.Vpc; - constructor(scope: Construct, id: string, props?: Props) { - super(scope, id, props); - - this.vpc = new ec2.Vpc(this, "vpc", { - subnetConfiguration: [ - { - cidrMask: 24, - name: "ingress", - subnetType: ec2.SubnetType.PUBLIC, - }, - { - cidrMask: 24, - name: "application", - subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, - }, - { - cidrMask: 28, - name: "rds", - subnetType: ec2.SubnetType.PRIVATE_ISOLATED, - }, - ], - natGateways: props?.natGatewayCount, - }); - - this.vpc.addGatewayEndpoint("DynamoDbEndpoint", { - service: ec2.GatewayVpcEndpointAwsService.DYNAMODB, - }); - - this.vpc.addInterfaceEndpoint("SecretsManagerEndpoint", { - service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, - }); - - this.vpc.addGatewayEndpoint("S3Endpoint", { - service: ec2.GatewayVpcEndpointAwsService.S3, - }); - - this.exportValue(this.vpc.selectSubnets({subnetType: ec2.SubnetType.PUBLIC}).subnets[0].subnetId) - this.exportValue(this.vpc.selectSubnets({subnetType: ec2.SubnetType.PUBLIC}).subnets[1].subnetId) - - } -} - -export interface Props extends StackProps { - /** - * Count of natGateways - * - * @default - One per availability zone - */ - natGatewayCount?: ec2.VpcProps["natGateways"]; -} - diff --git a/cdk/__init__.py b/cdk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cdk/config.py b/cdk/config.py new file mode 100644 index 0000000..917db24 --- /dev/null +++ b/cdk/config.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from typing import Literal, Optional + +from aws_cdk import aws_ec2 as ec2 +from pydantic import AliasChoices, BaseModel, Field, computed_field, field_validator, model_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class CollectionTransactionsConfig(BaseModel): + auth_mode: Literal["basic", "jwt"] + auth_secret_arn: Optional[str] = None + + +class Config(BaseSettings): + model_config = SettingsConfigDict( + env_ignore_empty=True, + arbitrary_types_allowed=True, + populate_by_name=True, + ) + + # --- Required --- + stage: str + db_instance_type: ec2.InstanceType + jwks_url: str + titiler_data_access_role_arn: str + ingestor_data_access_role_arn: str + stac_api_integration_api_arn: str + db_allocated_storage: int + mosaic_host: str + stac_browser_repo_tag: str + stac_browser_custom_domain_name: str + stac_browser_certificate_arn: str + stac_api_custom_domain_name: str + pgstac_version: str + web_acl_arn: str + + # --- Optional --- + version: str = "0.1.1" + certificate_arn: Optional[str] = None + ingestor_domain_name: Optional[str] = None + # env var is TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME (no underscore between pg/stac) + titiler_pg_stac_api_custom_domain_name: Optional[str] = Field( + None, + validation_alias=AliasChoices( + "titiler_pgstac_api_custom_domain_name", + "titiler_pg_stac_api_custom_domain_name", + ), + ) + user_stac_item_gen_role_arn: Optional[str] = None + user_stac_stac_api_custom_domain_name: Optional[str] = None + user_stac_titiler_pgstac_api_custom_domain_name: Optional[str] = None + user_stac_inbound_topic_arns: Optional[list[str]] = None + user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None + + # --- Collection transactions sub-fields (assembled by model_validator below) --- + user_stac_collection_transactions_enabled: Optional[bool] = None + user_stac_collection_transactions_auth_mode: Optional[str] = None + user_stac_collection_transactions_auth_secret_arn: Optional[str] = None + user_stac_collection_transactions: Optional[CollectionTransactionsConfig] = None + + @field_validator("db_instance_type", mode="before") + @classmethod + def parse_instance_type(cls, v: object) -> ec2.InstanceType: + if isinstance(v, ec2.InstanceType): + return v + try: + return ec2.InstanceType(str(v)) + except Exception as e: + raise ValueError(f"Invalid DB_INSTANCE_TYPE: {v!r}") from e + + @model_validator(mode="after") + def assemble_collection_transactions(self) -> Config: + if self.user_stac_collection_transactions_enabled is not True: + return self + if not self.user_stac_collection_transactions_auth_mode: + raise ValueError( + "Must provide USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE " + "when USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true" + ) + self.user_stac_collection_transactions = CollectionTransactionsConfig( + auth_mode=self.user_stac_collection_transactions_auth_mode, + auth_secret_arn=self.user_stac_collection_transactions_auth_secret_arn, + ) + return self + + @computed_field # type: ignore[prop-decorator] + @property + def tags(self) -> dict[str, str]: + return {"project": "MAAP", "version": self.version, "stage": self.stage} + + def build_stack_name(self, service_id: str) -> str: + return f"MAAP-STAC-{self.stage}-{service_id}" diff --git a/cdk/constructs/DpsStacItemGenerator/index.ts b/cdk/constructs/DpsStacItemGenerator/index.ts deleted file mode 100644 index 9516d2e..0000000 --- a/cdk/constructs/DpsStacItemGenerator/index.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { - aws_ec2 as ec2, - aws_lambda as lambda, - aws_sqs as sqs, - aws_sns as sns, - aws_sns_subscriptions as snsSubscriptions, - aws_lambda_event_sources as lambdaEventSources, - aws_logs as logs, - Duration, - CfnOutput, -} from "aws-cdk-lib"; -import { Construct } from "constructs"; -import { Role } from "aws-cdk-lib/aws-iam"; - -export interface DpsStacItemGeneratorProps { - /** - * VPC into which the lambda should be deployed. - */ - readonly vpc?: ec2.IVpc; - - /** - * Subnet into which the lambda should be deployed. - */ - readonly subnetSelection?: ec2.SubnetSelection; - - /** - * The lambda runtime to use for the item generation function. - * - * @default lambda.Runtime.PYTHON_3_12 - */ - readonly lambdaRuntime?: lambda.Runtime; - - /** - * The timeout for the item generation lambda in seconds. - * - * - Generate STAC metadata - * - Publish results to SNS - * - * The SQS visibility timeout will be set to this value plus 10 seconds. - * - * @default 120 - */ - readonly lambdaTimeoutSeconds?: number; - - /** - * Memory size for the lambda function in MB. - * - * @default 1024 - */ - readonly memorySize?: number; - - /** - * Maximum number of concurrent executions. - * - * This controls how many item generation tasks can run simultaneously. - * Higher concurrency enables faster processing of large batches but - * may strain downstream systems or external data sources. - * - * @default 100 - */ - readonly maxConcurrency?: number; - - /** - * SQS batch size for lambda event source. - * - * This determines how many generation requests are processed together - * in a single lambda invocation. Unlike the loader, generation typically - * processes items individually, so smaller batch sizes are common. - * - * @default 10 - */ - readonly batchSize?: number; - - /** - * Additional environment variables for the lambda function. - * - * These will be merged with default environment variables including - * ITEM_LOAD_TOPIC_ARN and LOG_LEVEL. Use this for custom configuration - * or to pass credentials for external data sources. - */ - readonly environment?: { [key: string]: string }; - - /** - * ARN of the SNS topic to publish generated items to. - * - * This is typically the topic from a StacItemLoader construct. - * Generated STAC items will be published here for downstream - * processing and database insertion. - */ - readonly itemLoadTopicArn: string; - - /** - * ARNs of externally-managed SNS topics that trigger item generation. - * - * These topics are owned and managed in the account where the source S3 - * buckets live. The SQS queue will subscribe to each topic. For - * cross-account topics, the topic policy in the remote account must permit - * `sns:Subscribe` from this account. - * - * @default [] - */ - readonly inboundTopicArns?: string[]; - readonly roleArn: string; - - /** - * Registry mapping collection ID patterns to lists of authorized usernames. - * - * When a DPS job's STAC items already carry a collection ID and the - * submitting user is listed as authorized for that collection ID pattern, - * the item's collection ID is preserved instead of being replaced with the - * deterministic ID. Keys support glob wildcards (e.g. "maap-prefix-*"). - * - * @example - * { "my-collection": ["user1", "user2"], "maap-*": ["user3"] } - * - * @default {} (all items receive the deterministic collection ID) - */ - readonly userStacCollectionIdRegistry?: Record; - - /** - * Deployment stage for naming resources and exports. - * - * Used to create unique export names for each deployment stage, - * allowing multiple stage deployments in the same account. - * - * @default "default" - */ - readonly stage?: string; -} - -export class DpsStacItemGenerator extends Construct { - /** - * The SQS queue that buffers item generation requests. - * - * This queue receives messages from the SNS topic containing ItemRequest - * payloads. It's configured with a visibility timeout that matches the - * Lambda timeout plus buffer time to prevent duplicate processing. - */ - public readonly queue: sqs.Queue; - - /** - * Dead letter queue for failed item generation attempts. - * - * Messages that fail processing after 5 attempts are sent here for - * inspection and potential replay. This helps with debugging. - */ - public readonly deadLetterQueue: sqs.Queue; - - /** - * The Lambda function that generates STAC items - */ - public readonly lambdaFunction: lambda.Function; - - constructor(scope: Construct, id: string, props: DpsStacItemGeneratorProps) { - super(scope, id); - - const timeoutSeconds = props.lambdaTimeoutSeconds ?? 120; - const batchSize = props.batchSize ?? 10; - const lambdaRuntime = props.lambdaRuntime ?? lambda.Runtime.PYTHON_3_12; - const stage = props.stage ?? "default"; - - // Create dead letter queue - this.deadLetterQueue = new sqs.Queue(this, "DeadLetterQueue", { - retentionPeriod: Duration.days(14), - }); - - // Create main queue - this.queue = new sqs.Queue(this, "Queue", { - deliveryDelay: Duration.minutes(1), - visibilityTimeout: Duration.minutes(5), - encryption: sqs.QueueEncryption.SQS_MANAGED, - deadLetterQueue: { - maxReceiveCount: 5, - queue: this.deadLetterQueue, - }, - }); - - // Subscribe the queue to each externally-managed inbound topic - (props.inboundTopicArns ?? []).forEach((topicArn, index) => { - const topic = sns.Topic.fromTopicArn( - this, - `InboundTopic${index}`, - topicArn, - ); - topic.addSubscription(new snsSubscriptions.SqsSubscription(this.queue)); - }); - - this.lambdaFunction = new lambda.Function(this, "Function", { - runtime: lambdaRuntime, - role: Role.fromRoleArn(this, "dps-item-gen-role", props.roleArn), - handler: "dps_stac_item_generator.handler.handler", - code: lambda.Code.fromDockerBuild(__dirname, { - file: "runtime/Dockerfile", - platform: "linux/amd64", - buildArgs: { - PYTHON_VERSION: lambdaRuntime.toString().replace("python", ""), - }, - }), - memorySize: props.memorySize ?? 1024, - timeout: Duration.seconds(timeoutSeconds), - logRetention: logs.RetentionDays.ONE_WEEK, - environment: { - ITEM_LOAD_TOPIC_ARN: props.itemLoadTopicArn, - LOG_LEVEL: "INFO", - ...(props.userStacCollectionIdRegistry && { - USER_STAC_COLLECTION_ID_REGISTRY: JSON.stringify( - props.userStacCollectionIdRegistry, - ), - }), - ...props.environment, - }, - }); - - // Add SQS event source to the lambda - this.lambdaFunction.addEventSource( - new lambdaEventSources.SqsEventSource(this.queue, { - batchSize: batchSize, - reportBatchItemFailures: true, - maxConcurrency: props.maxConcurrency ?? 100, - }), - ); - - // Grant permissions to publish to the item load topic - // Note: This will be granted externally since we only have the ARN - // The consuming construct should handle this permission - - // Create outputs - new CfnOutput(this, "QueueUrl", { - value: this.queue.queueUrl, - description: "URL of the DpsStacItemGenerator SQS Queue", - exportName: `dps-stac-item-generator-queue-url-${stage}`, - }); - - new CfnOutput(this, "DeadLetterQueueUrl", { - value: this.deadLetterQueue.queueUrl, - description: "URL of the DpsStacItemGenerator Dead Letter Queue", - exportName: `dps-stac-item-generator-deadletter-queue-url-${stage}`, - }); - - new CfnOutput(this, "FunctionName", { - value: this.lambdaFunction.functionName, - description: "Name of the DpsStacItemGenerator Lambda Function", - exportName: `dps-stac-item-generator-function-name-${stage}`, - }); - } -} diff --git a/cdk/constructs/__init__.py b/cdk/constructs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cdk/constructs/dps_stac_item_generator.py b/cdk/constructs/dps_stac_item_generator.py new file mode 100644 index 0000000..cb7c93c --- /dev/null +++ b/cdk/constructs/dps_stac_item_generator.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +from aws_cdk import ( + CfnOutput, + Duration, + aws_ec2 as ec2, + aws_iam as iam, + aws_lambda as lambda_, + aws_lambda_event_sources as lambda_event_sources, + aws_logs as logs, + aws_sns as sns, + aws_sns_subscriptions as sns_subscriptions, + aws_sqs as sqs, +) +from constructs import Construct + +_CONSTRUCT_DIR = Path(__file__).parent / "DpsStacItemGenerator" + + +@dataclass +class DpsStacItemGeneratorProps: + item_load_topic_arn: str + """ARN of the SNS topic to publish generated items to. Typically the topic from a StacLoader construct.""" + role_arn: str + """ARN of the IAM role assumed by the item generation Lambda.""" + vpc: Optional[ec2.IVpc] = None + """VPC into which the Lambda should be deployed.""" + subnet_selection: Optional[ec2.SubnetSelection] = None + """Subnet into which the Lambda should be deployed.""" + lambda_runtime: Optional[lambda_.Runtime] = None + """Lambda runtime to use. Default: PYTHON_3_12.""" + lambda_timeout_seconds: Optional[int] = None + """Timeout for the item generation Lambda in seconds. The SQS visibility timeout is set to this plus 10s. Default: 120.""" + memory_size: Optional[int] = None + """Memory size for the Lambda function in MB. Default: 1024.""" + max_concurrency: Optional[int] = None + """Maximum number of concurrent Lambda executions. Default: 100.""" + batch_size: Optional[int] = None + """SQS batch size for the Lambda event source. Default: 10.""" + environment: Optional[dict[str, str]] = None + """Additional environment variables merged with defaults (ITEM_LOAD_TOPIC_ARN, LOG_LEVEL).""" + inbound_topic_arns: Optional[list[str]] = None + """ARNs of externally-managed SNS topics that trigger item generation. The SQS queue subscribes to each. Default: [].""" + user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None + """Registry mapping collection ID patterns to authorized usernames. Keys support glob wildcards. + Example: {"my-collection": ["user1", "user2"], "maap-*": ["user3"]} + Default: {} (all items receive the deterministic collection ID). + """ + stage: Optional[str] = None + """Deployment stage used for naming CloudFormation exports. Default: "default".""" + + +class DpsStacItemGenerator(Construct): + queue: sqs.Queue + """SQS queue that buffers item generation requests from SNS.""" + dead_letter_queue: sqs.Queue + """Dead letter queue for messages that fail processing after 5 attempts.""" + lambda_function: lambda_.Function + """Lambda function that generates STAC items from DPS job outputs.""" + + def __init__( + self, + scope: Construct, + id: str, + props: DpsStacItemGeneratorProps, + ) -> None: + super().__init__(scope, id) + + timeout_seconds = props.lambda_timeout_seconds or 120 + batch_size = props.batch_size or 10 + lambda_runtime = props.lambda_runtime or lambda_.Runtime.PYTHON_3_12 + stage = props.stage or "default" + + # Dead letter queue + self.dead_letter_queue = sqs.Queue( + self, + "DeadLetterQueue", + retention_period=Duration.days(14), + ) + + # Main queue + self.queue = sqs.Queue( + self, + "Queue", + delivery_delay=Duration.minutes(1), + visibility_timeout=Duration.minutes(5), + encryption=sqs.QueueEncryption.SQS_MANAGED, + dead_letter_queue=sqs.DeadLetterQueue( + max_receive_count=5, + queue=self.dead_letter_queue, + ), + ) + + # Subscribe to each externally-managed inbound topic + for i, topic_arn in enumerate(props.inbound_topic_arns or []): + topic = sns.Topic.from_topic_arn(self, f"InboundTopic{i}", topic_arn) + topic.add_subscription(sns_subscriptions.SqsSubscription(self.queue)) + + python_version = lambda_runtime.to_string().replace("python", "") + + self.lambda_function = lambda_.Function( + self, + "Function", + runtime=lambda_runtime, + role=iam.Role.from_role_arn(self, "dps-item-gen-role", props.role_arn), + handler="dps_stac_item_generator.handler.handler", + code=lambda_.Code.from_docker_build( + str(_CONSTRUCT_DIR), + file="runtime/Dockerfile", + platform="linux/amd64", + build_args={"PYTHON_VERSION": python_version}, + ), + memory_size=props.memory_size or 1024, + timeout=Duration.seconds(timeout_seconds), + log_retention=logs.RetentionDays.ONE_WEEK, + environment={ + "ITEM_LOAD_TOPIC_ARN": props.item_load_topic_arn, + "LOG_LEVEL": "INFO", + **( + { + "USER_STAC_COLLECTION_ID_REGISTRY": json.dumps( + props.user_stac_collection_id_registry + ) + } + if props.user_stac_collection_id_registry + else {} + ), + **(props.environment or {}), + }, + vpc=props.vpc, + vpc_subnets=props.subnet_selection, + ) + + # SQS event source + self.lambda_function.add_event_source( + lambda_event_sources.SqsEventSource( + self.queue, + batch_size=batch_size, + report_batch_item_failures=True, + max_concurrency=props.max_concurrency or 100, + ) + ) + + # CloudFormation outputs + CfnOutput( + self, + "QueueUrl", + value=self.queue.queue_url, + description="URL of the DpsStacItemGenerator SQS Queue", + export_name=f"dps-stac-item-generator-queue-url-{stage}", + ) + + CfnOutput( + self, + "DeadLetterQueueUrl", + value=self.dead_letter_queue.queue_url, + description="URL of the DpsStacItemGenerator Dead Letter Queue", + export_name=f"dps-stac-item-generator-deadletter-queue-url-{stage}", + ) + + CfnOutput( + self, + "FunctionName", + value=self.lambda_function.function_name, + description="Name of the DpsStacItemGenerator Lambda Function", + export_name=f"dps-stac-item-generator-function-name-{stage}", + ) diff --git a/cdk/maap_eoapi_common.py b/cdk/maap_eoapi_common.py new file mode 100644 index 0000000..de2a6d2 --- /dev/null +++ b/cdk/maap_eoapi_common.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import aws_cdk as cdk +from aws_cdk import aws_s3 as s3 +from constructs import Construct + + +class MaapEoapiCommon(cdk.Stack): + """ + MaapEoapiCommon Stack + + This stack contains shared resources that are used by both the pgSTAC and userSTAC stacks. + Any resources that need to be accessed or referenced by multiple stacks should be placed here + to avoid circular dependencies and ensure proper resource sharing. + + Examples of shared resources include: + - Logging buckets for centralized log collection + - Monitoring resources + - IAM roles or policies that are used across stacks + + This pattern ensures clean separation of concerns while enabling resource reuse + across the MAAP eoAPI infrastructure. + """ + + logging_bucket: s3.Bucket + """S3 bucket for centralized logging across all MAAP eoAPI stacks. + Used by both pgSTAC and userSTAC stacks for storing access logs and other operational logs. + """ + + def __init__( + self, + scope: Construct, + id: str, + *, + stage: str, # Used for naming resources. + **kwargs, + ) -> None: + super().__init__(scope, id, **kwargs) + + self.logging_bucket = s3.Bucket( + self, + "maapLoggingBucket", + access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE, + removal_policy=cdk.RemovalPolicy.RETAIN, + block_public_access=s3.BlockPublicAccess.BLOCK_ALL, + bucket_name=f"maap-service-logging-{stage}", + enforce_ssl=True, + lifecycle_rules=[ + s3.LifecycleRule( + enabled=True, + expiration=cdk.Duration.days(395), + ) + ], + ) diff --git a/cdk/patch_manager.py b/cdk/patch_manager.py new file mode 100644 index 0000000..f3dafad --- /dev/null +++ b/cdk/patch_manager.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import aws_cdk as cdk +from aws_cdk import aws_iam as iam +from aws_cdk.aws_ssm import ( + CfnMaintenanceWindow, + CfnMaintenanceWindowTarget, + CfnMaintenanceWindowTask, + StringParameter, +) +from constructs import Construct + +# Aliases for long nested types +_TaskInvocationParams = CfnMaintenanceWindowTask.TaskInvocationParametersProperty +_RunCmdParams = CfnMaintenanceWindowTask.MaintenanceWindowRunCommandParametersProperty + + +class PatchManagerStack(cdk.Stack): + def __init__( + self, + scope: Construct, + id: str, + *, + pgbouncer_param_names: list[str], + **kwargs, + ) -> None: + super().__init__(scope, id, **kwargs) + + # IAM role used by the maintenance window + maintenance_role = iam.Role( + self, + "MaintenanceWindowRole", + assumed_by=iam.ServicePrincipal("ssm.amazonaws.com"), + ) + + maintenance_role.add_to_policy( + iam.PolicyStatement( + actions=[ + "ssm:SendCommand", + "ssm:ListCommands", + "ssm:ListCommandInvocations", + ], + resources=["*"], + ) + ) + + # Maintenance Window + maintenance_window = CfnMaintenanceWindow( + self, + "PatchMaintenanceWindow", + name="patch-maintenance-window", + description="Weekly patching using AWS default patch baseline", + schedule="cron(0 7 ? * WED *)", # Wednesdays 07:00 UTC + duration=3, + cutoff=1, + allow_unassociated_targets=False, + ) + + instance_ids = [ + StringParameter.value_for_string_parameter(self, param_name) + for param_name in pgbouncer_param_names + ] + + # Target EC2 instances by instance ID + target = CfnMaintenanceWindowTarget( + self, + "PatchTarget", + window_id=maintenance_window.ref, + resource_type="INSTANCE", + targets=[ + CfnMaintenanceWindowTarget.TargetsProperty( + key="InstanceIds", + values=instance_ids, + ) + ], + ) + + # Patch task (Install) + CfnMaintenanceWindowTask( + self, + "PatchInstallTask", + window_id=maintenance_window.ref, + task_arn="AWS-RunPatchBaseline", + task_type="RUN_COMMAND", + priority=1, + max_concurrency="2", + max_errors="1", + service_role_arn=maintenance_role.role_arn, + targets=[ + CfnMaintenanceWindowTask.TargetProperty( + key="WindowTargetIds", + values=[target.ref], + ) + ], + task_invocation_parameters=_TaskInvocationParams( + maintenance_window_run_command_parameters=_RunCmdParams( + parameters={"Operation": ["Install"]}, + ) + ), + ) diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py new file mode 100644 index 0000000..5d32b11 --- /dev/null +++ b/cdk/pgstac_infra.py @@ -0,0 +1,736 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +import yaml +from aws_cdk import ( + Aws, + Duration, + RemovalPolicy, + Stack, + aws_apigateway as apigateway, + aws_apigatewayv2 as apigatewayv2, + aws_certificatemanager as acm, + aws_cloudfront as cloudfront, + aws_cloudfront_origins as origins, + aws_cloudwatch as cloudwatch, + aws_ec2 as ec2, + aws_iam as iam, + aws_lambda as lambda_, + aws_rds as rds, + aws_s3 as s3, + aws_secretsmanager as secretsmanager, + aws_ssm as ssm, +) +from constructs import Construct + +import eoapi_cdk + +from .constructs.dps_stac_item_generator import ( + DpsStacItemGenerator, + DpsStacItemGeneratorProps, +) + +_CDK_DIR = Path(__file__).parent + + +@dataclass +class PgStacDbConfig: + instance_type: ec2.InstanceType + """RDS instance type.""" + pgstac_version: str + """Version of pgstac to install on the database.""" + allocated_storage: int + """Allocated storage for the pgstac database.""" + subnet_public: bool + """Flag to control whether the database should be deployed into a public subnet.""" + + +@dataclass +class TitilerPgstacConfig: + buckets_path: str + """YAML file containing the list of buckets the titiler lambda should be granted access to.""" + data_access_role_arn: str + """ARN of IAM role that will be assumed by the titiler Lambda.""" + mosaic_host: Optional[str] = None + """mosaicjson DynamoDB host for titiler, in the form of aws-region/table-name.""" + custom_domain_name: Optional[str] = None + """Domain name to use for titiler pgstac API. If defined, a new custom domain name will be created. + Example: "titiler-pgstac-api.dit.maap-project.org" + """ + + +@dataclass +class CollectionTransactionsConfig: + auth_mode: str # "basic" | "jwt" + """Authentication mode for collection transactions.""" + auth_secret_arn: Optional[str] = None + """ARN of the Secrets Manager secret containing the auth credentials.""" + + +@dataclass +class StacApiConfig: + custom_domain_name: Optional[str] = None + """Domain name to use for the STAC API. If defined, a new custom domain will be created. + Example: "stac-api.dit.maap-project.org" + """ + integration_api_arn: Optional[str] = None + """STAC API API Gateway source ARN to be granted STAC API lambda invoke permission.""" + transactions: Optional[CollectionTransactionsConfig] = None + """Optional collection transaction support. When omitted, the API stays read-only.""" + + +@dataclass +class StacBrowserConfig: + repo_tag: str + """Tag of the stac-browser repo from https://github.com/radiantearth/stac-browser. + Example: "v3.2.0" + """ + custom_domain_name: str + """Domain name for use in CloudFront distribution for stac-browser. + Example: "stac-browser.maap-project.org" + """ + certificate_arn: str + """ARN of ACM certificate to use for the CloudFront Distribution (must be us-east-1). + Example: "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012" + """ + + +@dataclass +class IngestorConfig: + jwks_url: str + """URL of JWKS endpoint, provided as output from ASDI-Auth. + Example: "https://cognito-idp.{region}.amazonaws.com/{region}_{userpool_id}/.well-known/jwks.json" + """ + data_access_role_arn: str + """ARN of IAM role that will be assumed by the STAC Ingestor.""" + user_data_path: str + """Path to userdata.yaml.""" + domain_name: Optional[str] = None + """Domain name to use for CDN. If defined, a new CDN will be created. + Example: "stac.maap.xyz" + """ + + +@dataclass +class DpsStacItemGenConfig: + item_gen_role_arn: str + inbound_topic_arns: Optional[list[str]] = None + user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None + + +class PgStacInfra(Stack): + def __init__( + self, + scope: Construct, + id: str, + *, + vpc: ec2.Vpc, + stage: str, # Used for naming resources. + type: str, # Type of deployment, e.g. "public" or "internal". + version: str, # Used to correlate codebase versions to running services. + web_acl_arn: str, # ARN of WAF Web ACL to use for eoAPI custom domains. + logging_bucket_arn: str, # ARN of S3 bucket for logging. + pgstac_db_config: PgStacDbConfig, + titiler_pgstac_config: TitilerPgstacConfig, + stac_api_config: StacApiConfig, + certificate_arn: Optional[str] = None, # ARN of ACM certificate for eoAPI custom domains. + stac_browser_config: Optional[StacBrowserConfig] = None, # Omit to skip STAC Browser. + ingestor_config: Optional[IngestorConfig] = None, # Omit to skip STAC Ingestor. + dps_stac_item_gen_config: Optional[DpsStacItemGenConfig] = None, + add_stactools_item_generator: Optional[bool] = None, + **kwargs, + ) -> None: + super().__init__(scope, id, **kwargs) + + stack = Stack.of(self) + + # ── pgSTAC Database ──────────────────────────────────────────────── + pgstac_db = eoapi_cdk.PgStacDatabase( + self, + "pgstac-db", + vpc=vpc, + allow_major_version_upgrade=True, + engine=rds.DatabaseInstanceEngine.postgres( + version=rds.PostgresEngineVersion.VER_17 + ), + vpc_subnets=ec2.SubnetSelection( + subnet_type=( + ec2.SubnetType.PUBLIC + if pgstac_db_config.subnet_public + else ec2.SubnetType.PRIVATE_ISOLATED + ) + ), + allocated_storage=pgstac_db_config.allocated_storage, + instance_type=pgstac_db_config.instance_type, + add_pgbouncer=True, + add_patch_manager=False, + pgstac_version=pgstac_db_config.pgstac_version, + custom_resource_properties={ + "context": True, + "update_collection_extent": True, + "use_queue": True, + }, + bootstrapper_lambda_function_options={ + "timeout": Duration.minutes(15) + }, + parameters={"shared_preload_libraries": "pg_cron"}, + ) + + if pgstac_db.pgbouncer_instance_id: + ssm.StringParameter( + self, + "pgbouncer-instance-id-param", + parameter_name=f"/maap-eoapi/{stage}/{type}/pgbouncer-instance-id", + string_value=pgstac_db.pgbouncer_instance_id, + description=f"PgBouncer EC2 instance ID for MAAP eoAPI {type} stack ({stage})", + ) + + api_subnet_selection = ec2.SubnetSelection( + subnet_type=( + ec2.SubnetType.PUBLIC + if pgstac_db_config.subnet_public + else ec2.SubnetType.PRIVATE_WITH_EGRESS + ) + ) + + # ── Collection transactions config ───────────────────────────────── + transactions_config = stac_api_config.transactions + if transactions_config and transactions_config.auth_mode != "basic": + raise ValueError( + f"Unsupported STAC collection transaction auth mode: " + f"{transactions_config.auth_mode}" + ) + + if transactions_config: + if transactions_config.auth_secret_arn: + transaction_auth_secret: Optional[secretsmanager.ISecret] = ( + secretsmanager.Secret.from_secret_complete_arn( + self, + "stac-collection-transaction-auth-secret", + transactions_config.auth_secret_arn, + ) + ) + else: + transaction_auth_secret = secretsmanager.Secret( + self, + "stac-collection-transaction-auth-secret", + description=( + f"Basic auth secret for MAAP {type} STAC collection " + f"transactions ({stage})" + ), + secret_name=( + f"/maap-eoapi/{stage}/{type}" + "/stac-collection-transaction-basic-auth" + ), + generate_secret_string=secretsmanager.SecretStringGenerator( + secret_string_template=json.dumps( + {"username": f"maap-{type}-stac-writer"} + ), + generate_string_key="password", + exclude_punctuation=True, + ), + ) + else: + transaction_auth_secret = None + + stac_enabled_extensions = [ + "query", + "sort", + "fields", + "filter", + "free_text", + "pagination", + "collection_search", + *(["collection_transaction"] if transactions_config else []), + ] + + stac_api_env: dict[str, str] = { + "STAC_FASTAPI_TITLE": f"MAAP {type} STAC API ({stage})", + "STAC_FASTAPI_LANDING_ID": f"maap-{type}-stac-api-{stage}", + "STAC_FASTAPI_DESCRIPTION": ( + f"The {type} STAC API for the [MAAP project](https://maap-project.org)" + ), + "STAC_FASTAPI_VERSION": version, + "ENABLED_EXTENSIONS": ",".join(stac_enabled_extensions), + **( + { + "MAAP_TRANSACTION_AUTH_MODE": transactions_config.auth_mode, + "MAAP_TRANSACTION_AUTH_SECRET_ARN": transaction_auth_secret.secret_arn, # type: ignore[union-attr] + } + if transactions_config + else {} + ), + } + + stac_api_lambda_options = { + "code": lambda_.Code.from_docker_build( + str(_CDK_DIR), + file="dockerfiles/Dockerfile.stac", + target_stage="lambda", + build_args={"PYTHON_VERSION": "3.12"}, + ), + "handler": "eoapi.stac.handler.handler", + } + + # ── STAC API ─────────────────────────────────────────────────────── + stac_api_domain_name = ( + apigatewayv2.DomainName( + self, + "stac-api-domain-name", + domain_name=stac_api_config.custom_domain_name, + certificate=acm.Certificate.from_certificate_arn( + self, + "stacApiCustomDomainNameCertificate", + certificate_arn, + ), + ) + if stac_api_config.custom_domain_name and certificate_arn + else None + ) + + stac_api_lambda = eoapi_cdk.PgStacApiLambda( + self, + "pgstac-api", + api_env=stac_api_env, + vpc=vpc, + db=pgstac_db.connection_target, + db_secret=pgstac_db.pgstac_secret, + subnet_selection=api_subnet_selection, + stac_api_domain_name=stac_api_domain_name, + enable_snap_start=True, + lambda_function_options=stac_api_lambda_options, + ) + + stac_api_lambda.lambda_function.connections.allow_to( + pgstac_db.connection_target, + ec2.Port.tcp(5432), + "allow connections from stac-fastapi-pgstac", + ) + + if stac_api_config.integration_api_arn: + stac_api_lambda.lambda_function.add_permission( + "ApiGatewayInvoke", + principal=iam.ServicePrincipal("apigateway.amazonaws.com"), + source_arn=stac_api_config.integration_api_arn, + ) + + if transaction_auth_secret: + transaction_auth_secret.grant_read(stac_api_lambda.lambda_function) + + ssm.StringParameter( + self, + "stac-collection-transaction-auth-secret-param", + parameter_name=( + f"/maap-eoapi/{stage}/{type}" + "/stac-collection-transaction-auth-secret-arn" + ), + string_value=transaction_auth_secret.secret_arn, + description=( + f"Secrets Manager ARN for MAAP {type} STAC collection " + f"transaction auth ({stage})" + ), + ) + + # ── titiler-pgstac ───────────────────────────────────────────────── + titiler_data_access_role = iam.Role.from_role_arn( + self, + "titiler-data-access-role", + titiler_pgstac_config.data_access_role_arn, + ) + + with open(titiler_pgstac_config.buckets_path, "r") as f: + buckets: list[str] = yaml.safe_load(f) + + titiler_pgstac_lambda_options = { + "code": lambda_.Code.from_docker_build( + str(_CDK_DIR), + file="dockerfiles/Dockerfile.raster", + target_stage="lambda", + build_args={"PYTHON_VERSION": "3.12"}, + ), + "handler": "handler.handler", + "role": titiler_data_access_role, + } + + titiler_pgstac_api_env: dict[str, str] = { + "NAME": f"MAAP titiler pgstac API ({stage})", + "VERSION": version, + "DESCRIPTION": "titiler pgstac API for the MAAP STAC system.", + } + + if titiler_pgstac_config.mosaic_host: + titiler_pgstac_api_env["MOSAIC_BACKEND"] = "dynamodb://" + titiler_pgstac_api_env["MOSAIC_HOST"] = titiler_pgstac_config.mosaic_host + + titiler_pgstac_domain_name = ( + apigatewayv2.DomainName( + self, + "titiler-pgstac-api-domain-name", + domain_name=titiler_pgstac_config.custom_domain_name, + certificate=acm.Certificate.from_certificate_arn( + self, + "titilerPgStacCustomDomainNameCertificate", + certificate_arn, + ), + ) + if titiler_pgstac_config.custom_domain_name and certificate_arn + else None + ) + + titiler_pgstac_api = eoapi_cdk.TitilerPgstacApiLambda( + self, + "titiler-pgstac-api", + api_env=titiler_pgstac_api_env, + vpc=vpc, + db=pgstac_db.connection_target, + db_secret=pgstac_db.pgstac_secret, + subnet_selection=api_subnet_selection, + buckets=buckets, + titiler_pgstac_api_domain_name=titiler_pgstac_domain_name, + lambda_function_options=titiler_pgstac_lambda_options, + enable_snap_start=True, + ) + + if titiler_pgstac_config.mosaic_host: + table_name = titiler_pgstac_config.mosaic_host.split("/", 2)[1] + + mosaic_perms = [ + iam.PolicyStatement( + actions=[ + "dynamodb:CreateTable", + "dynamodb:DescribeTable", + ], + resources=[ + f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/*" + ], + ), + iam.PolicyStatement( + actions=[ + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:PutItem", + "dynamodb:BatchWriteItem", + ], + resources=[ + f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{table_name}" + ], + ), + ] + + for permission in mosaic_perms: + titiler_pgstac_api.lambda_function.add_to_role_policy(permission) + + titiler_pgstac_api.lambda_function.connections.allow_to( + pgstac_db.connection_target, + ec2.Port.tcp(5432), + "allow connections from titiler", + ) + + # ── CloudWatch dashboard ─────────────────────────────────────────── + eoapi_dashboard = cloudwatch.Dashboard( + self, + "eoAPIDashboard", + dashboard_name=f"eoAPI-{stage}-{type}", + ) + + titiler_log_group = titiler_pgstac_api.lambda_function.log_group.log_group_name + + titiler_route_log_widget = cloudwatch.LogQueryWidget( + log_group_names=[titiler_log_group], + title="titiler requests by route", + width=12, + height=8, + view=cloudwatch.LogQueryVisualizationType.TABLE, + query_lines=[ + "fields @timestamp, @message", + 'filter @message like "Request:"', + 'parse @message \'"route": "*",\' as route', + "stats count(*) as count by route", + "sort count desc", + "limit 20", + ], + ) + + titiler_referer_analysis_widget = cloudwatch.LogQueryWidget( + log_group_names=[titiler_log_group], + title="titiler requests by request referer", + width=6, + height=8, + view=cloudwatch.LogQueryVisualizationType.TABLE, + query_lines=[ + "fields @timestamp, @message", + 'filter @message like "Request:"', + 'parse @message \'"referer": "*"\' as referer', + "stats count(*) as count by referer", + "sort count desc", + "limit 20", + ], + ) + + titiler_url_analysis_widget = cloudwatch.LogQueryWidget( + log_group_names=[titiler_log_group], + title="titiler /cog requests by url scheme and netloc", + width=6, + height=8, + view=cloudwatch.LogQueryVisualizationType.TABLE, + query_lines=[ + "fields @timestamp, @message", + 'filter @message like "Request:"', + 'parse @message \'"url_scheme": "*"\' as url_scheme', + 'parse @message \'"url_netloc": "*"\' as url_netloc', + "filter ispresent(url_scheme)", + "stats count(*) as count by url_scheme, url_netloc", + "sort count desc", + "limit 20", + ], + ) + + titiler_collection_analysis_widget = cloudwatch.LogQueryWidget( + log_group_names=[titiler_log_group], + title="titiler /collections requests by collection id", + width=6, + height=8, + view=cloudwatch.LogQueryVisualizationType.TABLE, + query_lines=[ + "fields @timestamp, @message", + 'filter @message like "Request:"', + 'parse @message \'"route": "*"\' as route', + 'filter route like "/collections/"', + "parse @message '\"path_params\": {*}' as path_params", + "stats count(*) as count by path_params.collection_id as collection_id", + "sort count desc", + "limit 20", + ], + ) + + titiler_searches_analysis_widget = cloudwatch.LogQueryWidget( + log_group_names=[titiler_log_group], + title="titiler /searches requests by search id", + width=6, + height=8, + view=cloudwatch.LogQueryVisualizationType.TABLE, + query_lines=[ + "fields @timestamp, @message", + 'filter @message like "Request:"', + 'parse @message \'"route": "*"\' as route', + 'filter route like "/searches/"', + "parse @message '\"path_params\": {*}' as path_params", + "stats count(*) as count by path_params.search_id as search_id", + "sort count desc", + "limit 20", + ], + ) + + eoapi_dashboard.add_widgets( + titiler_route_log_widget, + titiler_collection_analysis_widget, + titiler_searches_analysis_widget, + titiler_url_analysis_widget, + titiler_referer_analysis_widget, + ) + + # ── STAC Ingestor ────────────────────────────────────────────────── + if ingestor_config: + ingestor_data_access_role = iam.Role.from_role_arn( + self, + "ingestor-data-access-role", + ingestor_config.data_access_role_arn, + ) + + ingestor_domain_name_options = ( + apigateway.DomainNameOptions( + domain_name=ingestor_config.domain_name, + certificate=acm.Certificate.from_certificate_arn( + self, + "ingestorCustomDomainNameCertificate", + certificate_arn, + ), + ) + if ingestor_config.domain_name and certificate_arn + else None + ) + + eoapi_cdk.StacIngestor( + self, + "stac-ingestor", + vpc=vpc, + stac_url=stac_api_lambda.url, + data_access_role=ingestor_data_access_role, + stage=stage, + stac_db_secret=pgstac_db.pgstac_secret, + stac_db_security_group=pgstac_db.security_group, + subnet_selection=ec2.SubnetSelection( + subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS + ), + api_env={ + "JWKS_URL": ingestor_config.jwks_url, + "REQUESTER_PAYS": "true", + }, + pgstac_version=pgstac_db_config.pgstac_version, + ingestor_domain_name_options=ingestor_domain_name_options, + ) + + # ── STAC Browser ─────────────────────────────────────────────────── + log_bucket = s3.Bucket.from_bucket_attributes( + self, "LoggingBucket", bucket_arn=logging_bucket_arn + ) + + if stac_browser_config: + root_path = "index.html" + + stac_browser_bucket = s3.Bucket( + self, + "stacBrowserBucket", + access_control=s3.BucketAccessControl.PRIVATE, + removal_policy=RemovalPolicy.DESTROY, + block_public_access=s3.BlockPublicAccess.BLOCK_ALL, + bucket_name=f"maap-stac-browser-{stage}", + enforce_ssl=True, + ) + + stac_browser_origin = cloudfront.Distribution( + self, + "stacBrowserDistro", + default_behavior=cloudfront.BehaviorOptions( + origin=origins.S3Origin(stac_browser_bucket) + ), + default_root_object=root_path, + domain_names=[stac_browser_config.custom_domain_name], + certificate=acm.Certificate.from_certificate_arn( + self, + "stacBrowserCustomDomainNameCertificate", + stac_browser_config.certificate_arn, + ), + enable_logging=True, + log_bucket=log_bucket, + log_file_prefix=f"stac-browser-{type}", + error_responses=[ + cloudfront.ErrorResponse( + http_status=403, + response_http_status=200, + response_page_path=f"/{root_path}", + ttl=Duration.seconds(0), + ), + cloudfront.ErrorResponse( + http_status=404, + response_http_status=200, + response_page_path=f"/{root_path}", + ttl=Duration.seconds(0), + ), + ], + web_acl_id=web_acl_arn, + ) + + stac_catalog_url = ( + stac_api_config.custom_domain_name + if stac_api_config.custom_domain_name + and stac_api_config.custom_domain_name.startswith("https://") + else ( + f"https://{stac_api_config.custom_domain_name}/" + if stac_api_config.custom_domain_name + else stac_api_lambda.url + ) + ) + + eoapi_cdk.StacBrowser( + self, + "stac-browser", + bucket_arn=stac_browser_bucket.bucket_arn, + stac_catalog_url=stac_catalog_url, + github_repo_tag=stac_browser_config.repo_tag, + website_index_document=root_path, + ) + + account_id = Aws.ACCOUNT_ID + distribution_arn = ( + f"arn:aws:cloudfront::{account_id}:distribution/" + f"{stac_browser_origin.distribution_id}" + ) + + stac_browser_bucket.add_to_resource_policy( + iam.PolicyStatement( + sid="AllowCloudFrontServicePrincipal", + effect=iam.Effect.ALLOW, + actions=["s3:GetObject"], + principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")], + resources=[stac_browser_bucket.arn_for_objects("*")], + conditions={ + "StringEquals": {"aws:SourceArn": distribution_arn} + }, + ) + ) + + log_bucket.add_to_resource_policy( + iam.PolicyStatement( + sid="AllowCloudFrontServicePrincipal", + effect=iam.Effect.ALLOW, + actions=["s3:PutObject"], + resources=[log_bucket.arn_for_objects("AWSLogs/*")], + principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")], + conditions={ + "StringEquals": {"aws:SourceArn": distribution_arn} + }, + ) + ) + + # ── STAC item loader ─────────────────────────────────────────────── + stac_loader = eoapi_cdk.StacLoader( + self, + "stac-item-loader", + pgstac_db=pgstac_db, + vpc=vpc, + subnet_selection=api_subnet_selection, + batch_size=500, + lambda_timeout_seconds=300, + max_batching_window_minutes=5, + environment={"CREATE_COLLECTIONS_IF_MISSING": "TRUE"}, + ) + + pgstac_db.pgstac_secret.grant_read(stac_loader.lambda_function) + + stac_loader.lambda_function.connections.allow_to( + pgstac_db.connection_target, + ec2.Port.tcp(5432), + "allow connections from stac-item-loader", + ) + + # ── Item generators ──────────────────────────────────────────────── + if add_stactools_item_generator: + stactools_item_generator = eoapi_cdk.StactoolsItemGenerator( + self, + "stactools-item-generator", + item_load_topic_arn=stac_loader.topic.topic_arn, + vpc=vpc, + subnet_selection=api_subnet_selection, + ) + stactools_item_generator.lambda_function.add_to_role_policy( + iam.PolicyStatement( + actions=["s3:GetObject"], + resources=["arn:aws:s3:::*/*"], + ) + ) + stac_loader.topic.grant_publish(stactools_item_generator.lambda_function) + + if dps_stac_item_gen_config: + dps_stac_item_generator = DpsStacItemGenerator( + self, + "dps-item-generator", + DpsStacItemGeneratorProps( + item_load_topic_arn=stac_loader.topic.topic_arn, + role_arn=dps_stac_item_gen_config.item_gen_role_arn, + inbound_topic_arns=dps_stac_item_gen_config.inbound_topic_arns, + user_stac_collection_id_registry=( + dps_stac_item_gen_config.user_stac_collection_id_registry + ), + vpc=vpc, + subnet_selection=api_subnet_selection, + stage=stage, + ), + ) + + stac_loader.topic.grant_publish(dps_stac_item_generator.lambda_function) diff --git a/cdk/vpc.py b/cdk/vpc.py new file mode 100644 index 0000000..2ac3d9d --- /dev/null +++ b/cdk/vpc.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import Optional + +import aws_cdk as cdk +from aws_cdk import aws_ec2 as ec2 +from constructs import Construct + + +class VpcStack(cdk.Stack): + vpc: ec2.Vpc + + def __init__( + self, + scope: Construct, + id: str, + *, + nat_gateway_count: Optional[int] = None, # Default: one per availability zone. + **kwargs, + ) -> None: + super().__init__(scope, id, **kwargs) + + self.vpc = ec2.Vpc( + self, + "vpc", + subnet_configuration=[ + ec2.SubnetConfiguration( + cidr_mask=24, + name="ingress", + subnet_type=ec2.SubnetType.PUBLIC, + ), + ec2.SubnetConfiguration( + cidr_mask=24, + name="application", + subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS, + ), + ec2.SubnetConfiguration( + cidr_mask=28, + name="rds", + subnet_type=ec2.SubnetType.PRIVATE_ISOLATED, + ), + ], + nat_gateways=nat_gateway_count, + ) + + self.vpc.add_gateway_endpoint( + "DynamoDbEndpoint", + service=ec2.GatewayVpcEndpointAwsService.DYNAMODB, + ) + + self.vpc.add_interface_endpoint( + "SecretsManagerEndpoint", + service=ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, + ) + + self.vpc.add_gateway_endpoint( + "S3Endpoint", + service=ec2.GatewayVpcEndpointAwsService.S3, + ) + + public_subnets = self.vpc.select_subnets( + subnet_type=ec2.SubnetType.PUBLIC + ).subnets + self.export_value(public_subnets[0].subnet_id) + self.export_value(public_subnets[1].subnet_id) diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 08263b8..0000000 --- a/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - } -}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index ac1011c..0000000 --- a/package-lock.json +++ /dev/null @@ -1,5450 +0,0 @@ -{ - "name": "ts_app", - "version": "0.1.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ts_app", - "version": "0.1.1", - "dependencies": { - "aws-cdk-lib": "^2.220.0", - "constructs": "^10.3.0", - "eoapi-cdk": "^11.5.0", - "source-map-support": "^0.5.16" - }, - "bin": { - "ts_app": "bin/ts_app.js" - }, - "devDependencies": { - "@types/jest": "^26.0.10", - "@types/js-yaml": "4.0.5", - "@types/node": "10.17.27", - "aws-cdk": "^2.1100.1", - "jest": "^30.2.0", - "js-yaml": "^4.1.0", - "ts-jest": "^29.4.4", - "ts-node": "^9.0.0", - "typescript": "^5.0.0" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.242", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.242.tgz", - "integrity": "sha512-4c1bAy2ISzcdKXYS1k4HYZsNrgiwbiDzj36ybwFVxEWZXVAP0dimQTCaB9fxu7sWzEjw3d+eaw6Fon+QTfTIpQ==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", - "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "48.20.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-48.20.0.tgz", - "integrity": "sha512-+eeiav9LY4wbF/EFuCt/vfvi/Zoxo8bf94PW5clbMraChEliq83w4TbRVy0jB9jE0v1ooFTtIjSQkowSPkfISg==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "~1.4.1", - "semver": "^7.7.2" - }, - "engines": { - "node": ">= 18.0.0" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.7.2", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "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.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "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-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "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-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, - "license": "MIT", - "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.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "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, - "license": "MIT" - }, - "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@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, - "license": "ISC", - "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/load-nyc-config/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, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/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, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "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, - "license": "MIT", - "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.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", - "dev": true, - "dependencies": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "node_modules/@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "10.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", - "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==", - "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, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "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, - "license": "MIT", - "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, - "license": "ISC", - "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": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aws-cdk": { - "version": "2.1100.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1100.1.tgz", - "integrity": "sha512-q2poFrQh90TK6eqeI0zznA8r1JkDI63WVOSqC7gFGo6qjQjAnvFk/utxHoNRgAC0RL0CLd19uCcHh3jfX9NiSg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 18.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.233.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.233.0.tgz", - "integrity": "sha512-rBOzIA8TGC5eB8TyVIvckAVlX7a0/gVPE634FguhSee9RFaovjgc5+IixGyyLJhu3lLsMSjqDoqTJg2ab+p8ng==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "2.2.242", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^48.20.0", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.3.2", - "ignore": "^5.3.2", - "jsonschema": "^1.5.0", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.7.3", - "table": "^6.9.0", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 18.0.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.17.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.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.12", - "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/fast-uri": { - "version": "3.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.3.2", - "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.2", - "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.2.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.5.0", - "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/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.7.3", - "inBundle": true, - "license": "ISC", - "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.9.0", - "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/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@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-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "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, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz", - "integrity": "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "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, - "license": "Apache-2.0", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001749", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz", - "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "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, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/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, - "license": "MIT" - }, - "node_modules/cliui/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, - "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/cliui/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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, - "license": "MIT", - "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/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/constructs": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", - "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", - "license": "Apache-2.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, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz", - "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==", - "dev": true, - "license": "ISC" - }, - "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, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eoapi-cdk": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/eoapi-cdk/-/eoapi-cdk-11.5.0.tgz", - "integrity": "sha512-Uzlb/URd66nqyYkf1xdvlh6+4mx5/VcSrPlF9uAJ64Og5pqp7/E9NPfcW5UWMXjpVjTE3Q3cgRXcOi3jdZxXvA==", - "license": "ISC", - "peerDependencies": { - "aws-cdk-lib": "^2.220.0", - "constructs": "^10.4.2" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "BSD-2-Clause", - "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, - "license": "MIT", - "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/execa/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, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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, - "license": "ISC" - }, - "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/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, - "license": "MIT", - "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, - "license": "ISC", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "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/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, - "license": "MIT" - }, - "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, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "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==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "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, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "ISC" - }, - "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, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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, - "license": "BSD-3-Clause", - "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": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-haste-map/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-leak-detector/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, - "license": "MIT", - "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, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "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, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "ISC", - "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, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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, - "license": "BSD-3-Clause", - "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, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "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, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "ISC", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/pretty-format/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/pretty-format/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "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, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "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, - "license": "BSD-3-Clause" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "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, - "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/string-width-cjs/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, - "license": "MIT" - }, - "node_modules/string-width-cjs/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "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, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "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/test-exclude/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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, - "license": "BSD-3-Clause" - }, - "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, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.4.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.4.tgz", - "integrity": "sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "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, - "license": "MIT", - "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, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "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, - "license": "Apache-2.0", - "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, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "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, - "license": "MIT", - "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/wrap-ansi-cjs/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, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/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, - "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/wrap-ansi-cjs/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?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, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.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, - "license": "ISC", - "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, - "license": "ISC" - }, - "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, - "license": "MIT", - "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, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/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, - "license": "MIT" - }, - "node_modules/yargs/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, - "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/yargs/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 1e9fc57..0000000 --- a/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "ts_app", - "version": "0.1.1", - "bin": { - "ts_app": "bin/ts_app.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk", - "cdk:watch": "cdk watch --all" - }, - "devDependencies": { - "@types/jest": "^26.0.10", - "@types/js-yaml": "4.0.5", - "@types/node": "10.17.27", - "aws-cdk": "^2.1100.1", - "jest": "^30.2.0", - "js-yaml": "^4.1.0", - "ts-jest": "^29.4.4", - "ts-node": "^9.0.0", - "typescript": "^5.0.0" - }, - "dependencies": { - "aws-cdk-lib": "^2.220.0", - "constructs": "^10.3.0", - "eoapi-cdk": "^11.5.0", - "source-map-support": "^0.5.16" - } -} diff --git a/pyproject.toml b/pyproject.toml index fe5bbd8..e3d92f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,13 @@ name = "maap-eoapi" version = "0.0" requires-python = ">=3.12" +dependencies = [ + "aws-cdk-lib==2.220.0", + "constructs==10.4.2", + "eoapi-cdk>=11.6.0", + "pydantic-settings>=2.0", + "pyyaml>=6.0", +] [tool.uv.sources] dps-stac-item-generator = { path = "cdk/constructs/DpsStacItemGenerator/runtime" } @@ -12,6 +19,10 @@ eoapi-stac = { path = "cdk/runtimes/eoapi/stac" } [dependency-groups] dev = [ "aws-lambda-typing>=2.20.0", + "boto3>=1.34", + "pytest>=8.0", + "pystac[validation]>=1.0", + "requests>=2.31", ] diff --git a/test/README.md b/test/README.md deleted file mode 100644 index da81808..0000000 --- a/test/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Config Tests - -This directory contains tests for the MAAP EOAPI configuration module. - -## Running Tests - -To run the tests, execute the following command from the project root: - -```bash -npm test -``` - -## Test Coverage - -The test suite covers the following aspects of the config.ts module: - -1. **Basic Configuration Parsing**: Tests that environment variables are correctly parsed into the Config object. - -2. **Required Variables**: Tests that the config module correctly throws errors when required environment variables are missing. - -3. **JSON Parsing**: Tests the parsing of JSON-formatted environment variables, such as BASTION_HOST_IPV4_ALLOW_LIST. - -4. **Error Handling**: Tests graceful handling of malformed JSON in environment variables. - -5. **Optional Variables**: Tests that optional environment variables are correctly set when provided. - -6. **Stack Name Generation**: Tests the buildStackName helper function. - -## Adding New Tests - -When adding new configuration options to the Config class, make sure to: - -1. Add corresponding tests to ensure the new configuration is parsed correctly. -2. Update the test setup in the beforeEach() function to include default values for any new required environment variables. - -## Test Environment - -The tests use Jest's mocking capabilities to simulate various environment variable configurations without affecting your actual environment variables. \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..5510ca5 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,46 @@ +# Tests + +This directory contains two test suites for the MAAP eoAPI infrastructure. + +## CDK Unit Tests + +Tests for the CDK stacks and constructs. These synthesise CloudFormation templates +and assert on their structure — no AWS credentials or deployed resources required. + +```bash +uv run pytest tests/test_pgstac_infra.py +``` + +**Test files:** +- **`test_pgstac_infra.py`** — Tests for the `PgStacInfra` stack, covering STAC API construct + configuration and collection transaction auth secret handling. + +**Test coverage:** +1. Default stack configuration: verifies the STAC API lambda is configured with the custom + Docker handler and that collection transactions are disabled by default. +2. Stack-managed transaction secret: when `CollectionTransactionsConfig` is provided without + an explicit `auth_secret_arn`, a new Secrets Manager secret is generated by the stack. +3. Explicit transaction secret ARN: when an `auth_secret_arn` is provided, the existing secret + is imported rather than a new one being created. + +**Adding new CDK tests:** + +Use `build_template()` to construct a minimal `assertions.Template` with only the props your +test needs, then assert with `template.has_resource_properties()` or `template.resource_count_is()`. + +--- + +## Integration Tests + +End-to-end ingestion tests that run against a live deployed environment. +Requires AWS credentials and a running deployment. + +```bash +uv run --group dev pytest tests/test_stac_ingestion.py +``` + +**Test files:** +- **`test_stac_ingestion.py`** — Validates STAC collection and item ingestion against a live stack. +- **`conftest.py`** — Shared fixtures (STAC ingestion client, test collection/item payloads). +- **`ingestion.py`** — Helper client for interacting with the STAC ingestor API. +- **`fixtures/`** — Sample STAC collection and item JSON used by the integration tests. diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index 5f27621..0000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -pytest -boto3 -pystac -pystac[validation] -requests \ No newline at end of file diff --git a/tests/test_pgstac_infra.py b/tests/test_pgstac_infra.py new file mode 100644 index 0000000..74afe83 --- /dev/null +++ b/tests/test_pgstac_infra.py @@ -0,0 +1,217 @@ +"""Tests for PgStacInfra stack - Python equivalent of test/pgstac-infra.test.ts""" +from __future__ import annotations + +from unittest.mock import patch + +import aws_cdk as cdk +from aws_cdk import assertions, aws_ec2 as ec2 + +from cdk.pgstac_infra import ( + CollectionTransactionsConfig, + PgStacDbConfig, + PgStacInfra, + StacApiConfig, + TitilerPgstacConfig, +) + +# Minimal required props for test builds +BASE_TITILER_CONFIG = TitilerPgstacConfig( + mosaic_host="example.com/table-name", + buckets_path="./titiler_buckets.yaml", + data_access_role_arn="arn:aws:iam::123456789012:role/test-role", + custom_domain_name="titiler.example.com", +) + +BASE_PGSTAC_DB_CONFIG = PgStacDbConfig( + instance_type=ec2.InstanceType("t3.micro"), + subnet_public=False, + allocated_storage=20, + pgstac_version="0.9.5", +) + + +def build_template(overrides: dict | None = None) -> assertions.Template: + app = cdk.App() + network_stack = cdk.Stack(app, "NetworkStack") + vpc = ec2.Vpc( + network_stack, + "Vpc", + max_azs=2, + nat_gateways=1, + subnet_configuration=[ + ec2.SubnetConfiguration(name="public", subnet_type=ec2.SubnetType.PUBLIC), + ec2.SubnetConfiguration( + name="private", subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS + ), + ec2.SubnetConfiguration( + name="isolated", subnet_type=ec2.SubnetType.PRIVATE_ISOLATED + ), + ], + ) + + props = dict( + vpc=vpc, + stage="test", + type="internal", + version="1.0.0", + web_acl_arn="arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl", + logging_bucket_arn="arn:aws:s3:::test-logging-bucket", + pgstac_db_config=BASE_PGSTAC_DB_CONFIG, + stac_api_config=StacApiConfig(custom_domain_name="stac-api.example.com"), + titiler_pgstac_config=BASE_TITILER_CONFIG, + ) + props |= (overrides or {}) + + # Mock lambda Code.from_docker_build so tests don't need Docker + mock_code = cdk.aws_lambda.Code.from_asset("test") + with patch("aws_cdk.aws_lambda.Code.from_docker_build", return_value=mock_code): + stack = PgStacInfra(app, "TestPgStacInfra", **props) + + return assertions.Template.from_stack(stack) + + +class TestPgStacInfraStacRuntimeWiring: + def test_uses_custom_stac_handler_and_keeps_transactions_disabled_by_default(self): + template = build_template( + { + "type": "public", + "stac_api_config": StacApiConfig( + custom_domain_name="public-stac.example.com", + integration_api_arn=( + "arn:aws:execute-api:us-west-2:123456789012:api-id/stage/GET/" + ), + ), + } + ) + + template.has_resource_properties( + "AWS::Lambda::Function", + { + "Handler": "eoapi.stac.handler.handler", + "Environment": { + "Variables": assertions.Match.object_like( + { + "STAC_FASTAPI_TITLE": "MAAP public STAC API (test)", + "STAC_FASTAPI_LANDING_ID": "maap-public-stac-api-test", + "ENABLED_EXTENSIONS": ( + "query,sort,fields,filter,free_text," + "pagination,collection_search" + ), + } + ) + }, + }, + ) + + # No transaction secret should exist + secrets = template.find_resources( + "AWS::SecretsManager::Secret", + { + "Properties": { + "Name": "/maap-eoapi/test/public/stac-collection-transaction-basic-auth" + } + }, + ) + assert len(secrets) == 0 + template.resource_count_is("AWS::SSM::Parameter", 1) + + def test_enables_collection_transactions_with_stack_managed_secret(self): + template = build_template( + { + "stac_api_config": StacApiConfig( + custom_domain_name="internal-stac.example.com", + transactions=CollectionTransactionsConfig(auth_mode="basic"), + ), + } + ) + + template.has_resource_properties( + "AWS::SecretsManager::Secret", + { + "Description": ( + "Basic auth secret for MAAP internal STAC collection transactions (test)" + ), + "Name": ( + "/maap-eoapi/test/internal/stac-collection-transaction-basic-auth" + ), + "GenerateSecretString": assertions.Match.object_like( + { + "GenerateStringKey": "password", + "SecretStringTemplate": '{"username":"maap-internal-stac-writer"}', + } + ), + }, + ) + + template.has_resource_properties( + "AWS::Lambda::Function", + { + "Handler": "eoapi.stac.handler.handler", + "Environment": { + "Variables": assertions.Match.object_like( + { + "ENABLED_EXTENSIONS": ( + "query,sort,fields,filter,free_text,pagination," + "collection_search,collection_transaction" + ), + "MAAP_TRANSACTION_AUTH_MODE": "basic", + } + ) + }, + }, + ) + + template.has_resource_properties( + "AWS::SSM::Parameter", + { + "Name": ( + "/maap-eoapi/test/internal/stac-collection-transaction-auth-secret-arn" + ) + }, + ) + + def test_uses_explicit_transaction_auth_secret_arn_override(self): + template = build_template( + { + "stac_api_config": StacApiConfig( + custom_domain_name="internal-stac.example.com", + transactions=CollectionTransactionsConfig( + auth_mode="basic", + auth_secret_arn=( + "arn:aws:secretsmanager:us-west-2:123456789012:" + "secret:existing-auth-abcdef" + ), + ), + ), + } + ) + + # No managed secret should be created + secrets = template.find_resources( + "AWS::SecretsManager::Secret", + { + "Properties": { + "Name": ( + "/maap-eoapi/test/internal/stac-collection-transaction-basic-auth" + ) + } + }, + ) + assert len(secrets) == 0 + + template.has_resource_properties( + "AWS::Lambda::Function", + { + "Handler": "eoapi.stac.handler.handler", + "Environment": { + "Variables": assertions.Match.object_like( + { + "MAAP_TRANSACTION_AUTH_SECRET_ARN": ( + "arn:aws:secretsmanager:us-west-2:123456789012:" + "secret:existing-auth-abcdef" + ) + } + ) + }, + }, + ) diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index a74ab40..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2018", - "module": "commonjs", - "lib": [ - "es2018" - ], - "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, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} \ No newline at end of file diff --git a/uv.lock b/uv.lock index 27618d6..43f7e2f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,85 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "aws-cdk-asset-awscli-v1" +version = "2.2.242" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/66/095e92652c175a9c18c98bc358db2c5957897245053fb5d0988c908be355/aws_cdk_asset_awscli_v1-2.2.242.tar.gz", hash = "sha256:a957d679a118f4375307ed90b9aed7127c5c1402989438060eae4ab29ab0d13f", size = 19284036, upload-time = "2025-06-23T17:42:03.275Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/ca/0415b7387c776c0a82a153fe75573e78cbbf1a71d4475636393f5ecfc649/aws_cdk_asset_awscli_v1-2.2.242-py3-none-any.whl", hash = "sha256:d1001bf56a12f7d1162d4211003d1e8f72a213159465e2d0e1c598cc0ea44aad", size = 19282441, upload-time = "2025-06-23T17:42:00.381Z" }, +] + +[[package]] +name = "aws-cdk-asset-node-proxy-agent-v6" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/e3/92e49ecaa002b4ac54371538991389c248577cbae7394ea9d1261ff41078/aws_cdk_asset_node_proxy_agent_v6-2.1.2.tar.gz", hash = "sha256:1340588dd351dcae37e07188f39075364f73ccde6ed9468c1184b06ada4af665", size = 1534674, upload-time = "2026-05-15T08:14:19.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/06/29837eb8ae4dc82ed2e593e4b24449394393b9a8a247b0ce2cee811e5cbb/aws_cdk_asset_node_proxy_agent_v6-2.1.2-py3-none-any.whl", hash = "sha256:1028bff16fdb87b8c82404e9b48f32ed383429dece84067f47379c4049da5da4", size = 1533233, upload-time = "2026-05-15T08:14:17.622Z" }, +] + +[[package]] +name = "aws-cdk-cloud-assembly-schema" +version = "48.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/b5/1ce2f6bff913ca8c94a001b84290ec4ce3729f54a3af0e3ff0edb303ac20/aws_cdk_cloud_assembly_schema-48.20.0.tar.gz", hash = "sha256:229aa136c26b71b0a82b5a32658eabcd30e344f7e136315fdb6e3de8ef523bfa", size = 208109, upload-time = "2025-11-19T12:19:48.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/08/17a35f0b668451484f2254f5e50a0105958bffe90da11c41b7629972e6a9/aws_cdk_cloud_assembly_schema-48.20.0-py3-none-any.whl", hash = "sha256:f5b6cf661cac8690add9461de13aeae3f3742eec71c066032bd045b08d0b7c3e", size = 207669, upload-time = "2025-11-19T12:19:46.614Z" }, +] + +[[package]] +name = "aws-cdk-lib" +version = "2.220.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aws-cdk-asset-awscli-v1" }, + { name = "aws-cdk-asset-node-proxy-agent-v6" }, + { name = "aws-cdk-cloud-assembly-schema" }, + { name = "constructs" }, + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/fa/22bd5a46f672a2591f0701522477cf143f9f5ca255268b39b9cc101fe5d2/aws_cdk_lib-2.220.0.tar.gz", hash = "sha256:0c5242dd740e5c0f31cf7f65f6f7b4fecada0d5e4054570944c5308230d797ae", size = 44854427, upload-time = "2025-10-14T13:58:13.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/01/00d6a7fde042b7bf447f13a0cc18ee355949b278dfb0690cab9f87dd47e8/aws_cdk_lib-2.220.0-py3-none-any.whl", hash = "sha256:2f69bae74529142a8decb3d1591cf54c8ed9fc033e503a09bfbdd534509a6785", size = 45113171, upload-time = "2025-10-14T13:57:43.205Z" }, +] + [[package]] name = "aws-lambda-typing" version = "2.20.0" @@ -11,17 +89,713 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/3d/4031f5950d65e89136d20a18b5ac985d5f087c29c902f285b6e8063619e4/aws_lambda_typing-2.20.0-py3-none-any.whl", hash = "sha256:1d44264cabfeab5ac38e67ddd0c874e677b2cbbae77a42d0519df470e6bbb49b", size = 35296, upload-time = "2024-04-02T09:43:05.881Z" }, ] +[[package]] +name = "boto3" +version = "1.43.31" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/8a/1d33e395da9319b162046ff7d7e75550756425c2a236d8682b4a1bbdec1c/boto3-1.43.31.tar.gz", hash = "sha256:8165b79c02955affbe4b4e9aa7c560684d0d4d86b4b9de66a37b45eb79fc4b69", size = 113122, upload-time = "2026-06-16T19:45:02.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/66/67b54f12fc0c5b2dc6fd0c04ea4cf1e7fc4a9843de680a22070d1fd77901/boto3-1.43.31-py3-none-any.whl", hash = "sha256:69c5521ad864f33d4d53b0e18a3927697f4558210693b1cb4dd97da959d1f7b9", size = 140533, upload-time = "2026-06-16T19:44:59.93Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.31" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/17/225ae5e7253441ae3beadf26aa12c2f9ce292f386a4877a2ed0bb17d6014/botocore-1.43.31.tar.gz", hash = "sha256:c249625faaa353c5b4004043706a394a4f3bcd3643e242f6b01fff6dc70e988b", size = 15540929, upload-time = "2026-06-16T19:44:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1e/da5fdcb8438008d3c53a0c6de6a40cb10bdb7e02a6e034aef79bc2b66800/botocore-1.43.31-py3-none-any.whl", hash = "sha256:4c51c63f39515fc1a2b8e3e2c29e452009c988ba55ad489251658fdd3aedad6e", size = 15223619, upload-time = "2026-06-16T19:44:43.116Z" }, +] + +[[package]] +name = "cattrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "constructs" +version = "10.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/84/f608a0a71a05a476b2f1761ab8f3f776677d39f7996ecf1092a1ce741a7c/constructs-10.4.2.tar.gz", hash = "sha256:ce54724360fffe10bab27d8a081844eb81f5ace7d7c62c84b719c49f164d5307", size = 65434, upload-time = "2024-10-14T12:58:02.822Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/d9/c5e7458f323bf063a9a54200742f2494e2ce3c7c6873e0ff80f88033c75f/constructs-10.4.2-py3-none-any.whl", hash = "sha256:1f0f59b004edebfde0f826340698b8c34611f57848139b7954904c61645f13c1", size = 63509, upload-time = "2024-10-14T12:57:59.828Z" }, +] + +[[package]] +name = "eoapi-cdk" +version = "11.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aws-cdk-lib" }, + { name = "constructs" }, + { name = "jsii" }, + { name = "publication" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/9e/2409e31e0f14531a05e72cc8c90dca40869685f6f9be7e6aeea0e15d9a88/eoapi_cdk-11.6.0.tar.gz", hash = "sha256:20b043d51fa904010cd93fb1520b2b9ceb93cb13eb1dfee621928dc6aca13bee", size = 631355, upload-time = "2026-05-28T16:03:33.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/80/56e179a27e609fc7dba8d19c2964de1ec91d185d8770457868ec7927f421/eoapi_cdk-11.6.0-py3-none-any.whl", hash = "sha256:8186ab34487be5149bc82d17935fdde3ba5697caca1203e50a1e3bc2b2128ff9", size = 629803, upload-time = "2026-05-28T16:03:32.134Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "jsii" +version = "1.135.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, + { name = "publication" }, + { name = "python-dateutil" }, + { name = "typeguard" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/51/a6843b1e382ad4eb8558d4b8b416c742a609e05c6f8676a73e9288c73c91/jsii-1.135.0.tar.gz", hash = "sha256:3d860404e9e350beae0bdae48b473173653bd2be3a901c620feb656b690f9c2f", size = 450497, upload-time = "2026-06-11T14:19:00.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b2/ea42aaea59de50a310721ed6954ceeee3aea867a94cc52c1f59674dd0778/jsii-1.135.0-py3-none-any.whl", hash = "sha256:bfab0cc3f99bfbc13683af2afb8d62522af867fb826fc825ab6b27fe636bb087", size = 423786, upload-time = "2026-06-11T14:18:58.263Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "maap-eoapi" version = "0.0" source = { virtual = "." } +dependencies = [ + { name = "aws-cdk-lib" }, + { name = "constructs" }, + { name = "eoapi-cdk" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, +] [package.dev-dependencies] dev = [ { name = "aws-lambda-typing" }, + { name = "boto3" }, + { name = "pystac", extra = ["validation"] }, + { name = "pytest" }, + { name = "requests" }, ] [package.metadata] +requires-dist = [ + { name = "aws-cdk-lib", specifier = "==2.220.0" }, + { name = "constructs", specifier = "==10.4.2" }, + { name = "eoapi-cdk", specifier = ">=11.6.0" }, + { name = "pydantic-settings", specifier = ">=2.0" }, + { name = "pyyaml", specifier = ">=6.0" }, +] [package.metadata.requires-dev] -dev = [{ name = "aws-lambda-typing", specifier = ">=2.20.0" }] +dev = [ + { name = "aws-lambda-typing", specifier = ">=2.20.0" }, + { name = "boto3", specifier = ">=1.34" }, + { name = "pystac", extras = ["validation"], specifier = ">=1.0" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "requests", specifier = ">=2.31" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "publication" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/8e/8c9fe7e32fdf9c386f83d59610cc819a25dadb874b5920f2d0ef7d35f46d/publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4", size = 5484, upload-time = "2019-01-15T07:52:23.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/d3/6308debad7afcdb3ea5f50b4b3d852f41eb566a311fbcb4da23755a28155/publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6", size = 7687, upload-time = "2019-01-15T07:52:22.151Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pystac" +version = "1.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/e6/efbc20dbc94ad7ed18fe11a4208103a509384ffcccd9bdc27953b725e686/pystac-1.14.3.tar.gz", hash = "sha256:24f92d6f301371859aa0abc1bbe7b1523a603e1184a6d139ecb323967c2c9bb3", size = 164205, upload-time = "2026-01-09T12:38:42.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/b4/a9430e72bfc3c458e1fcf8363890994e483052ab052ed93912be4e5b32c8/pystac-1.14.3-py3-none-any.whl", hash = "sha256:2f60005f521d541fb801428307098f223c14697b3faf4d2f0209afb6a43f39e5", size = 208506, upload-time = "2026-01-09T12:38:40.721Z" }, +] + +[package.optional-dependencies] +validation = [ + { name = "jsonschema" }, +] + +[[package]] +name = "pytest" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "typeguard" +version = "2.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604, upload-time = "2021-12-10T21:09:39.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605, upload-time = "2021-12-10T21:09:37.844Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] From 596df30e2911d3f4a5774fe2baf37aae451ddbe6 Mon Sep 17 00:00:00 2001 From: Jamison French <50224594+jjfrench@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:26:50 -0500 Subject: [PATCH 2/8] chore: append rather than unpack Co-authored-by: Henry Rodman --- cdk/pgstac_infra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index 5d32b11..fbfb9fd 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -245,8 +245,9 @@ def __init__( "free_text", "pagination", "collection_search", - *(["collection_transaction"] if transactions_config else []), ] + if transaction_config: + stac_enabled_extensions.append("collection_transaction") stac_api_env: dict[str, str] = { "STAC_FASTAPI_TITLE": f"MAAP {type} STAC API ({stage})", From 718179753202b7863b0a23200d35b0784b954a7c Mon Sep 17 00:00:00 2001 From: Jamison French Date: Fri, 19 Jun 2026 09:49:06 -0500 Subject: [PATCH 3/8] chore: remove parameter docstrings --- cdk/constructs/dps_stac_item_generator.py | 19 ------------ cdk/pgstac_infra.py | 35 ----------------------- 2 files changed, 54 deletions(-) diff --git a/cdk/constructs/dps_stac_item_generator.py b/cdk/constructs/dps_stac_item_generator.py index cb7c93c..33576cd 100644 --- a/cdk/constructs/dps_stac_item_generator.py +++ b/cdk/constructs/dps_stac_item_generator.py @@ -25,43 +25,24 @@ @dataclass class DpsStacItemGeneratorProps: item_load_topic_arn: str - """ARN of the SNS topic to publish generated items to. Typically the topic from a StacLoader construct.""" role_arn: str - """ARN of the IAM role assumed by the item generation Lambda.""" vpc: Optional[ec2.IVpc] = None - """VPC into which the Lambda should be deployed.""" subnet_selection: Optional[ec2.SubnetSelection] = None - """Subnet into which the Lambda should be deployed.""" lambda_runtime: Optional[lambda_.Runtime] = None - """Lambda runtime to use. Default: PYTHON_3_12.""" lambda_timeout_seconds: Optional[int] = None - """Timeout for the item generation Lambda in seconds. The SQS visibility timeout is set to this plus 10s. Default: 120.""" memory_size: Optional[int] = None - """Memory size for the Lambda function in MB. Default: 1024.""" max_concurrency: Optional[int] = None - """Maximum number of concurrent Lambda executions. Default: 100.""" batch_size: Optional[int] = None - """SQS batch size for the Lambda event source. Default: 10.""" environment: Optional[dict[str, str]] = None - """Additional environment variables merged with defaults (ITEM_LOAD_TOPIC_ARN, LOG_LEVEL).""" inbound_topic_arns: Optional[list[str]] = None - """ARNs of externally-managed SNS topics that trigger item generation. The SQS queue subscribes to each. Default: [].""" user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None - """Registry mapping collection ID patterns to authorized usernames. Keys support glob wildcards. - Example: {"my-collection": ["user1", "user2"], "maap-*": ["user3"]} - Default: {} (all items receive the deterministic collection ID). - """ stage: Optional[str] = None - """Deployment stage used for naming CloudFormation exports. Default: "default".""" class DpsStacItemGenerator(Construct): queue: sqs.Queue - """SQS queue that buffers item generation requests from SNS.""" dead_letter_queue: sqs.Queue - """Dead letter queue for messages that fail processing after 5 attempts.""" lambda_function: lambda_.Function - """Lambda function that generates STAC items from DPS job outputs.""" def __init__( self, diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index fbfb9fd..f5af031 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -40,79 +40,44 @@ @dataclass class PgStacDbConfig: instance_type: ec2.InstanceType - """RDS instance type.""" pgstac_version: str - """Version of pgstac to install on the database.""" allocated_storage: int - """Allocated storage for the pgstac database.""" subnet_public: bool - """Flag to control whether the database should be deployed into a public subnet.""" @dataclass class TitilerPgstacConfig: buckets_path: str - """YAML file containing the list of buckets the titiler lambda should be granted access to.""" data_access_role_arn: str - """ARN of IAM role that will be assumed by the titiler Lambda.""" mosaic_host: Optional[str] = None - """mosaicjson DynamoDB host for titiler, in the form of aws-region/table-name.""" custom_domain_name: Optional[str] = None - """Domain name to use for titiler pgstac API. If defined, a new custom domain name will be created. - Example: "titiler-pgstac-api.dit.maap-project.org" - """ @dataclass class CollectionTransactionsConfig: auth_mode: str # "basic" | "jwt" - """Authentication mode for collection transactions.""" auth_secret_arn: Optional[str] = None - """ARN of the Secrets Manager secret containing the auth credentials.""" @dataclass class StacApiConfig: custom_domain_name: Optional[str] = None - """Domain name to use for the STAC API. If defined, a new custom domain will be created. - Example: "stac-api.dit.maap-project.org" - """ integration_api_arn: Optional[str] = None - """STAC API API Gateway source ARN to be granted STAC API lambda invoke permission.""" transactions: Optional[CollectionTransactionsConfig] = None - """Optional collection transaction support. When omitted, the API stays read-only.""" - @dataclass class StacBrowserConfig: repo_tag: str - """Tag of the stac-browser repo from https://github.com/radiantearth/stac-browser. - Example: "v3.2.0" - """ custom_domain_name: str - """Domain name for use in CloudFront distribution for stac-browser. - Example: "stac-browser.maap-project.org" - """ certificate_arn: str - """ARN of ACM certificate to use for the CloudFront Distribution (must be us-east-1). - Example: "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012" - """ @dataclass class IngestorConfig: jwks_url: str - """URL of JWKS endpoint, provided as output from ASDI-Auth. - Example: "https://cognito-idp.{region}.amazonaws.com/{region}_{userpool_id}/.well-known/jwks.json" - """ data_access_role_arn: str - """ARN of IAM role that will be assumed by the STAC Ingestor.""" user_data_path: str - """Path to userdata.yaml.""" domain_name: Optional[str] = None - """Domain name to use for CDN. If defined, a new CDN will be created. - Example: "stac.maap.xyz" - """ @dataclass From ea115db5054caf33fbb84bd70a0f2faf13a81062 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Mon, 22 Jun 2026 12:39:46 -0500 Subject: [PATCH 4/8] chore: rebase --- cdk/pgstac_infra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index f5af031..68a54af 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -211,7 +211,7 @@ def __init__( "pagination", "collection_search", ] - if transaction_config: + if transactions_config: stac_enabled_extensions.append("collection_transaction") stac_api_env: dict[str, str] = { From ad1c172a6bcbedbab000369607d6459155901a25 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Mon, 22 Jun 2026 12:34:31 -0500 Subject: [PATCH 5/8] chore: move dataclasses --- cdk/config.py | 58 ++++++++++++++++++++++++++++++-- cdk/pgstac_infra.py | 82 +++++++++++---------------------------------- 2 files changed, 75 insertions(+), 65 deletions(-) diff --git a/cdk/config.py b/cdk/config.py index 917db24..5131705 100644 --- a/cdk/config.py +++ b/cdk/config.py @@ -1,17 +1,69 @@ from __future__ import annotations from typing import Literal, Optional - +from dataclasses import dataclass from aws_cdk import aws_ec2 as ec2 -from pydantic import AliasChoices, BaseModel, Field, computed_field, field_validator, model_validator +from pydantic import ( + AliasChoices, + Field, + computed_field, + field_validator, + model_validator, +) from pydantic_settings import BaseSettings, SettingsConfigDict -class CollectionTransactionsConfig(BaseModel): +@dataclass +class PgStacDbConfig: + instance_type: ec2.InstanceType + pgstac_version: str + allocated_storage: int + subnet_public: bool + + +@dataclass +class TitilerPgstacConfig: + buckets_path: str + data_access_role_arn: str + mosaic_host: Optional[str] = None + custom_domain_name: Optional[str] = None + + +@dataclass +class CollectionTransactionsConfig: auth_mode: Literal["basic", "jwt"] auth_secret_arn: Optional[str] = None +@dataclass +class StacApiConfig: + custom_domain_name: Optional[str] = None + integration_api_arn: Optional[str] = None + transactions: Optional[CollectionTransactionsConfig] = None + + +@dataclass +class StacBrowserConfig: + repo_tag: str + custom_domain_name: str + certificate_arn: str + + +@dataclass +class IngestorConfig: + jwks_url: str + data_access_role_arn: str + user_data_path: str + domain_name: Optional[str] = None + + +@dataclass +class DpsStacItemGenConfig: + item_gen_role_arn: str + inbound_topic_arns: Optional[list[str]] = None + user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None + + class Config(BaseSettings): model_config = SettingsConfigDict( env_ignore_empty=True, diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index 68a54af..64e30ff 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -1,7 +1,7 @@ from __future__ import annotations import json -from dataclasses import dataclass + from pathlib import Path from typing import Optional @@ -25,6 +25,16 @@ aws_secretsmanager as secretsmanager, aws_ssm as ssm, ) + +from .config import ( + PgStacDbConfig, + TitilerPgstacConfig, + StacApiConfig, + StacBrowserConfig, + IngestorConfig, + DpsStacItemGenConfig, +) + from constructs import Construct import eoapi_cdk @@ -37,56 +47,6 @@ _CDK_DIR = Path(__file__).parent -@dataclass -class PgStacDbConfig: - instance_type: ec2.InstanceType - pgstac_version: str - allocated_storage: int - subnet_public: bool - - -@dataclass -class TitilerPgstacConfig: - buckets_path: str - data_access_role_arn: str - mosaic_host: Optional[str] = None - custom_domain_name: Optional[str] = None - - -@dataclass -class CollectionTransactionsConfig: - auth_mode: str # "basic" | "jwt" - auth_secret_arn: Optional[str] = None - - -@dataclass -class StacApiConfig: - custom_domain_name: Optional[str] = None - integration_api_arn: Optional[str] = None - transactions: Optional[CollectionTransactionsConfig] = None - -@dataclass -class StacBrowserConfig: - repo_tag: str - custom_domain_name: str - certificate_arn: str - - -@dataclass -class IngestorConfig: - jwks_url: str - data_access_role_arn: str - user_data_path: str - domain_name: Optional[str] = None - - -@dataclass -class DpsStacItemGenConfig: - item_gen_role_arn: str - inbound_topic_arns: Optional[list[str]] = None - user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None - - class PgStacInfra(Stack): def __init__( self, @@ -102,8 +62,12 @@ def __init__( pgstac_db_config: PgStacDbConfig, titiler_pgstac_config: TitilerPgstacConfig, stac_api_config: StacApiConfig, - certificate_arn: Optional[str] = None, # ARN of ACM certificate for eoAPI custom domains. - stac_browser_config: Optional[StacBrowserConfig] = None, # Omit to skip STAC Browser. + certificate_arn: Optional[ + str + ] = None, # ARN of ACM certificate for eoAPI custom domains. + stac_browser_config: Optional[ + StacBrowserConfig + ] = None, # Omit to skip STAC Browser. ingestor_config: Optional[IngestorConfig] = None, # Omit to skip STAC Ingestor. dps_stac_item_gen_config: Optional[DpsStacItemGenConfig] = None, add_stactools_item_generator: Optional[bool] = None, @@ -139,9 +103,7 @@ def __init__( "update_collection_extent": True, "use_queue": True, }, - bootstrapper_lambda_function_options={ - "timeout": Duration.minutes(15) - }, + bootstrapper_lambda_function_options={"timeout": Duration.minutes(15)}, parameters={"shared_preload_libraries": "pg_cron"}, ) @@ -625,9 +587,7 @@ def __init__( actions=["s3:GetObject"], principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")], resources=[stac_browser_bucket.arn_for_objects("*")], - conditions={ - "StringEquals": {"aws:SourceArn": distribution_arn} - }, + conditions={"StringEquals": {"aws:SourceArn": distribution_arn}}, ) ) @@ -638,9 +598,7 @@ def __init__( actions=["s3:PutObject"], resources=[log_bucket.arn_for_objects("AWSLogs/*")], principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")], - conditions={ - "StringEquals": {"aws:SourceArn": distribution_arn} - }, + conditions={"StringEquals": {"aws:SourceArn": distribution_arn}}, ) ) From 32158ef4b2769662846d00b03ed237cc689ea111 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Mon, 22 Jun 2026 12:35:33 -0500 Subject: [PATCH 6/8] style: ruff format --- .../runtime/tests/test_item.py | 16 ++++-- cdk/runtimes/eoapi/stac/tests/test_app.py | 34 +++++++++--- cdk/runtimes/eoapi/stac/tests/test_auth.py | 9 +++- tests/conftest.py | 3 +- tests/ingestion.py | 1 - tests/test_pgstac_infra.py | 3 +- tests/test_stac_ingestion.py | 53 ++++++++++++++----- 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/cdk/constructs/DpsStacItemGenerator/runtime/tests/test_item.py b/cdk/constructs/DpsStacItemGenerator/runtime/tests/test_item.py index b0350db..fff54a9 100644 --- a/cdk/constructs/DpsStacItemGenerator/runtime/tests/test_item.py +++ b/cdk/constructs/DpsStacItemGenerator/runtime/tests/test_item.py @@ -305,7 +305,9 @@ def test_authorized_collection_id_preserved(self, mock_catalog, mock_job_metadat return_value=mock_job_metadata, ), ): - items = list(get_stac_items(catalog_s3_key, collection_id_registry=registry)) + items = list( + get_stac_items(catalog_s3_key, collection_id_registry=registry) + ) for item in items: assert item.collection == "test-collection" @@ -326,7 +328,9 @@ def test_unauthorized_collection_id_replaced(self, mock_catalog, mock_job_metada return_value=mock_job_metadata, ), ): - items = list(get_stac_items(catalog_s3_key, collection_id_registry=registry)) + items = list( + get_stac_items(catalog_s3_key, collection_id_registry=registry) + ) for item in items: assert item.collection == expected_collection_id @@ -346,12 +350,16 @@ def test_wildcard_registry_pattern(self, mock_catalog, mock_job_metadata): return_value=mock_job_metadata, ), ): - items = list(get_stac_items(catalog_s3_key, collection_id_registry=registry)) + items = list( + get_stac_items(catalog_s3_key, collection_id_registry=registry) + ) for item in items: assert item.collection == "test-collection" - def test_empty_registry_uses_deterministic_id(self, mock_catalog, mock_job_metadata): + def test_empty_registry_uses_deterministic_id( + self, mock_catalog, mock_job_metadata + ): """An empty registry results in the deterministic collection ID for all items.""" catalog_s3_key = "s3://test-bucket/2023/01/15/10/30/45/123456/catalog.json" expected_collection_id = "superman__awesome-algo__0.1__test" diff --git a/cdk/runtimes/eoapi/stac/tests/test_app.py b/cdk/runtimes/eoapi/stac/tests/test_app.py index 7f2b5bf..d20dfb7 100644 --- a/cdk/runtimes/eoapi/stac/tests/test_app.py +++ b/cdk/runtimes/eoapi/stac/tests/test_app.py @@ -7,8 +7,11 @@ from eoapi.stac import auth from eoapi.stac.main import ( +<<<<<<< HEAD CATALOG_TRANSACTION_EXTENSION, CATALOGS_EXTENSION, +======= +>>>>>>> 8b10b607 (style: ruff format) COLLECTION_TRANSACTION_EXTENSION, create_app, parse_enabled_extensions, @@ -60,7 +63,9 @@ def test_read_only_app_omits_collection_transaction_routes() -> None: assert "/collections/{collection_id}/items" in openapi["paths"] assert set(openapi["paths"]["/collections/{collection_id}/items"].keys()) == {"get"} assert "/collections/{collection_id}/items/{item_id}" in openapi["paths"] - assert set(openapi["paths"]["/collections/{collection_id}/items/{item_id}"].keys()) == {"get"} + assert set( + openapi["paths"]["/collections/{collection_id}/items/{item_id}"].keys() + ) == {"get"} def test_catalog_routes_are_enabled_by_default() -> None: @@ -221,8 +226,13 @@ def test_collection_transaction_app_registers_collection_only_routes( "delete", } assert set(openapi["paths"]["/collections/{collection_id}/items"].keys()) == {"get"} - assert set(openapi["paths"]["/collections/{collection_id}/items/{item_id}"].keys()) == {"get"} - assert {parameter["name"] for parameter in openapi["paths"]["/collections"]["get"]["parameters"]} >= { + assert set( + openapi["paths"]["/collections/{collection_id}/items/{item_id}"].keys() + ) == {"get"} + assert { + parameter["name"] + for parameter in openapi["paths"]["/collections"]["get"]["parameters"] + } >= { "query", "sortby", } @@ -258,8 +268,12 @@ def test_openapi_and_conformance_advertise_collection_transactions_only( assert "/collections/test/items" not in openapi["paths"] assert "/collections/{collection_id}/items/{item_id}" in openapi["paths"] assert "put" not in openapi["paths"]["/collections/{collection_id}/items/{item_id}"] - assert "patch" not in openapi["paths"]["/collections/{collection_id}/items/{item_id}"] - assert "delete" not in openapi["paths"]["/collections/{collection_id}/items/{item_id}"] + assert ( + "patch" not in openapi["paths"]["/collections/{collection_id}/items/{item_id}"] + ) + assert ( + "delete" not in openapi["paths"]["/collections/{collection_id}/items/{item_id}"] + ) assert openapi["components"]["securitySchemes"]["HTTPBasic"] == { "type": "http", "scheme": "basic", @@ -281,8 +295,14 @@ def test_openapi_and_conformance_advertise_collection_transactions_only( assert response.status_code == 200 conformance_classes = response.json()["conformsTo"] - assert "https://api.stacspec.org/v1.0.0/collections/extensions/transaction" in conformance_classes - assert "https://api.stacspec.org/v1.0.0/ogcapi-features/extensions/transaction" not in conformance_classes + assert ( + "https://api.stacspec.org/v1.0.0/collections/extensions/transaction" + in conformance_classes + ) + assert ( + "https://api.stacspec.org/v1.0.0/ogcapi-features/extensions/transaction" + not in conformance_classes + ) def test_parse_enabled_extensions_rejects_malformed_values() -> None: diff --git a/cdk/runtimes/eoapi/stac/tests/test_auth.py b/cdk/runtimes/eoapi/stac/tests/test_auth.py index 93e0547..562cf4b 100644 --- a/cdk/runtimes/eoapi/stac/tests/test_auth.py +++ b/cdk/runtimes/eoapi/stac/tests/test_auth.py @@ -54,7 +54,11 @@ def collection_transaction_app( ) -> Iterator[TestClient]: """Build a transaction-enabled app using env-provided credentials.""" app = create_app( - enabled_extensions={"query", "collection_search", COLLECTION_TRANSACTION_EXTENSION}, + enabled_extensions={ + "query", + "collection_search", + COLLECTION_TRANSACTION_EXTENSION, + }, connect_to_database=False, ) with TestClient(app) as client: @@ -117,7 +121,8 @@ def test_collection_write_routes_receive_transaction_auth_dependency( protected_routes = { (route.path, next(iter(route.methods))): route for route in collection_transaction_app.app.routes - if getattr(route, "path", None) in {"/collections", "/collections/{collection_id}"} + if getattr(route, "path", None) + in {"/collections", "/collections/{collection_id}"} and getattr(route, "methods", None) and next(iter(route.methods)) in {"POST", "PUT", "PATCH", "DELETE"} } diff --git a/tests/conftest.py b/tests/conftest.py index 752a949..62feb97 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,7 @@ def test_collection(stac_ingestion_instance): def test_item(stac_ingestion_instance): return stac_ingestion_instance.get_test_item() + @pytest.fixture(scope="module") def test_titiler_search_request(stac_ingestion_instance): - return stac_ingestion_instance.get_test_titiler_search_request() \ No newline at end of file + return stac_ingestion_instance.get_test_titiler_search_request() diff --git a/tests/ingestion.py b/tests/ingestion.py index def987a..d343f06 100644 --- a/tests/ingestion.py +++ b/tests/ingestion.py @@ -7,7 +7,6 @@ class StacIngestion: - """Class representing various test operations""" def __init__(self): diff --git a/tests/test_pgstac_infra.py b/tests/test_pgstac_infra.py index 74afe83..d8c7d3f 100644 --- a/tests/test_pgstac_infra.py +++ b/tests/test_pgstac_infra.py @@ -1,4 +1,5 @@ """Tests for PgStacInfra stack - Python equivalent of test/pgstac-infra.test.ts""" + from __future__ import annotations from unittest.mock import patch @@ -60,7 +61,7 @@ def build_template(overrides: dict | None = None) -> assertions.Template: stac_api_config=StacApiConfig(custom_domain_name="stac-api.example.com"), titiler_pgstac_config=BASE_TITILER_CONFIG, ) - props |= (overrides or {}) + props |= overrides or {} # Mock lambda Code.from_docker_build so tests don't need Docker mock_code = cdk.aws_lambda.Code.from_asset("test") diff --git a/tests/test_stac_ingestion.py b/tests/test_stac_ingestion.py index 801a52e..30ee2f4 100644 --- a/tests/test_stac_ingestion.py +++ b/tests/test_stac_ingestion.py @@ -2,10 +2,11 @@ import pystac import time + # Test validating the collection def test_validate_collection(test_collection): pystac.validation.validate_dict(test_collection) - + # Test validating the item def test_validate_item(test_item): @@ -19,14 +20,19 @@ def test_insert_collection( response = stac_ingestion_instance.insert_collection( authentication_token, test_collection ) - assert response.status_code in [200, 201], f"Failed to insert the test_collection :\n{response.text}" + assert response.status_code in [200, 201], ( + f"Failed to insert the test_collection :\n{response.text}" + ) # Wait for the collection to be inserted time.sleep(60) + # Test inserting item def test_insert_item(stac_ingestion_instance, authentication_token, test_item): response = stac_ingestion_instance.insert_item(authentication_token, test_item) - assert response.status_code in [200, 201], f"Failed to insert the test_item :\n{response.text}" + assert response.status_code in [200, 201], ( + f"Failed to insert the test_item :\n{response.text}" + ) # Wait for the item to be inserted time.sleep(60) @@ -34,36 +40,57 @@ def test_insert_item(stac_ingestion_instance, authentication_token, test_item): # Test querying collection and verifying inserted collection def test_query_collection(stac_ingestion_instance, test_collection): response = stac_ingestion_instance.query_collection(test_collection["id"]) - assert response.status_code in [200, 201], f"Failed to query the test_collection :\n{response.text}" + assert response.status_code in [200, 201], ( + f"Failed to query the test_collection :\n{response.text}" + ) + # Test registering a mosaic and querying its assets -def test_titiler_pgstac(stac_ingestion_instance, test_titiler_search_request, test_item): - register_response = stac_ingestion_instance.register_mosaic(test_titiler_search_request) - assert register_response.status_code in [200, 201], f"Failed to register the mosaic :\n{register_response.text}" +def test_titiler_pgstac( + stac_ingestion_instance, test_titiler_search_request, test_item +): + register_response = stac_ingestion_instance.register_mosaic( + test_titiler_search_request + ) + assert register_response.status_code in [200, 201], ( + f"Failed to register the mosaic :\n{register_response.text}" + ) search_id = register_response.json()["id"] # allow for some time for the mosaic to be inserted time.sleep(10) asset_query_response = stac_ingestion_instance.list_mosaic_assets(search_id) - assert asset_query_response.status_code in [200, 201], f"Failed to query the mosaic's assets for mosaic {search_id} :\n{asset_query_response.text}" + assert asset_query_response.status_code in [200, 201], ( + f"Failed to query the mosaic's assets for mosaic {search_id} :\n{asset_query_response.text}" + ) assets_json = asset_query_response.json() # expects a single item in the collection assert len(assets_json) == 1 - assert all([k in assets_json[0]['assets'] for k in test_item['assets'].keys()]) + assert all([k in assets_json[0]["assets"] for k in test_item["assets"].keys()]) + # Test querying items and verifying inserted items def test_query_items(stac_ingestion_instance, test_collection, test_item): response = stac_ingestion_instance.query_items(test_collection["id"]) - assert response.status_code in [200, 201], f"Failed to query the items :\n{response.text}" + assert response.status_code in [200, 201], ( + f"Failed to query the items :\n{response.text}" + ) item = response.json()["features"][0] - assert item["id"] == test_item["id"], f"Inserted item - {test_item} \n not found in the queried items {item}" + assert item["id"] == test_item["id"], ( + f"Inserted item - {test_item} \n not found in the queried items {item}" + ) # Test querying collection and verifying inserted collection def test_delete_collection( stac_ingestion_instance, authentication_token, test_collection ): - response = stac_ingestion_instance.delete_collection(authentication_token, test_collection["id"]) - assert response.status_code in [200, 201], f"Failed to delete the test_collection :\n{response.text}" + response = stac_ingestion_instance.delete_collection( + authentication_token, test_collection["id"] + ) + assert response.status_code in [200, 201], ( + f"Failed to delete the test_collection :\n{response.text}" + ) + # Run the tests if __name__ == "__main__": From 3ea6bb1bc1c358d3980d62955ac54141d63167b6 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Wed, 1 Jul 2026 14:40:27 -0500 Subject: [PATCH 7/8] chore: rebase cleaned, PR suggestions --- .gitignore | 9 +- .npmignore | 6 - .nvmrc | 1 - app.py | 76 +-- cdk/PgStacInfra.ts | 797 ---------------------- cdk/app.ts | 147 ---- cdk/config.py | 155 ++++- cdk/config.ts | 286 -------- cdk/pgstac_infra.py | 48 +- cdk/runtimes/eoapi/stac/tests/test_app.py | 3 - test/config.test.ts | 205 ------ test/pgstac-infra.test.ts | 258 ------- tests/test_config.py | 167 +++++ tests/test_pgstac_infra.py | 107 ++- 14 files changed, 467 insertions(+), 1798 deletions(-) delete mode 100644 .npmignore delete mode 100644 .nvmrc delete mode 100644 cdk/PgStacInfra.ts delete mode 100644 cdk/app.ts delete mode 100644 cdk/config.ts delete mode 100644 test/config.test.ts delete mode 100644 test/pgstac-infra.test.ts create mode 100644 tests/test_config.py diff --git a/.gitignore b/.gitignore index ec45d7d..f4845f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,12 @@ -*.js -!jest.config.js -*.d.ts -node_modules - .pyc __pycache__ .pytest_cache .venv -.env -.envrc +.env* .env-test .test-env .DS_Store +.ruff_cache # CDK asset staging directory .cdk.staging diff --git a/.npmignore b/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 209e3ef..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -20 diff --git a/app.py b/app.py index d2bb8c9..fd0b8ba 100644 --- a/app.py +++ b/app.py @@ -4,20 +4,13 @@ from cdk.config import Config from cdk.maap_eoapi_common import MaapEoapiCommon from cdk.patch_manager import PatchManagerStack -from cdk.pgstac_infra import ( - DpsStacItemGenConfig, - IngestorConfig, - PgStacDbConfig, - PgStacInfra, - StacApiConfig, - StacBrowserConfig, - TitilerPgstacConfig, -) +from cdk.pgstac_infra import PgStacInfra from cdk.vpc import VpcStack config = Config() app = cdk.App() +dps_stac_item_gen_config = config.dps_stac_item_gen() vpc_stack = VpcStack( app, @@ -47,33 +40,11 @@ certificate_arn=config.certificate_arn, web_acl_arn=config.web_acl_arn, logging_bucket_arn=common.logging_bucket.bucket_arn, - pgstac_db_config=PgStacDbConfig( - instance_type=config.db_instance_type, - pgstac_version=config.pgstac_version, - allocated_storage=config.db_allocated_storage, - subnet_public=False, - ), - stac_api_config=StacApiConfig( - custom_domain_name=config.stac_api_custom_domain_name, - integration_api_arn=config.stac_api_integration_api_arn, - ), - titiler_pgstac_config=TitilerPgstacConfig( - mosaic_host=config.mosaic_host, - buckets_path="./titiler_buckets.yaml", - custom_domain_name=config.titiler_pg_stac_api_custom_domain_name, - data_access_role_arn=config.titiler_data_access_role_arn, - ), - stac_browser_config=StacBrowserConfig( - repo_tag=config.stac_browser_repo_tag, - custom_domain_name=config.stac_browser_custom_domain_name, - certificate_arn=config.stac_browser_certificate_arn, - ), - ingestor_config=IngestorConfig( - jwks_url=config.jwks_url, - data_access_role_arn=config.ingestor_data_access_role_arn, - domain_name=config.ingestor_domain_name, - user_data_path="./userdata.yaml", - ), + pgstac_db_config=config.pgstac_db(), + stac_api_config=config.stac_api(), + titiler_pgstac_config=config.titiler_pgstac(), + stac_browser_config=config.stac_browser(), + ingestor_config=config.ingestor(), add_stactools_item_generator=True, termination_protection=False, ) @@ -89,36 +60,11 @@ certificate_arn=config.certificate_arn, web_acl_arn=config.web_acl_arn, logging_bucket_arn=common.logging_bucket.bucket_arn, - pgstac_db_config=PgStacDbConfig( - instance_type=config.db_instance_type, - pgstac_version=config.pgstac_version, - allocated_storage=config.db_allocated_storage, - subnet_public=False, - ), - stac_api_config=StacApiConfig( - custom_domain_name=config.user_stac_stac_api_custom_domain_name, - transactions=( - config.user_stac_collection_transactions # type: ignore[arg-type] - ), - ), - titiler_pgstac_config=TitilerPgstacConfig( - mosaic_host=config.mosaic_host, - buckets_path="./titiler_buckets.yaml", - custom_domain_name=config.user_stac_titiler_pgstac_api_custom_domain_name, - data_access_role_arn=config.titiler_data_access_role_arn, - ), + pgstac_db_config=config.pgstac_db(), + stac_api_config=config.stac_api(user_stac=True), + titiler_pgstac_config=config.titiler_pgstac(user_stac=True), add_stactools_item_generator=False, - **( - { - "dps_stac_item_gen_config": DpsStacItemGenConfig( - item_gen_role_arn=config.user_stac_item_gen_role_arn, - inbound_topic_arns=config.user_stac_inbound_topic_arns, - user_stac_collection_id_registry=config.user_stac_collection_id_registry, - ) - } - if config.user_stac_item_gen_role_arn - else {} - ), + **({"dps_stac_item_gen_config": dps_stac_item_gen_config} if dps_stac_item_gen_config else {}), termination_protection=False, ) diff --git a/cdk/PgStacInfra.ts b/cdk/PgStacInfra.ts deleted file mode 100644 index 4053cef..0000000 --- a/cdk/PgStacInfra.ts +++ /dev/null @@ -1,797 +0,0 @@ -import { - aws_apigatewayv2 as apigatewayv2, - aws_certificatemanager as acm, - aws_iam as iam, - aws_ec2 as ec2, - aws_lambda as lambda, - aws_rds as rds, - aws_s3 as s3, - aws_secretsmanager as secretsmanager, - aws_cloudfront as cloudfront, - aws_cloudfront_origins as origins, - aws_cloudwatch as cloudwatch, - aws_ssm as ssm, -} from "aws-cdk-lib"; -import { Aws, Duration, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; -import { Construct } from "constructs"; -import { - CustomLambdaFunctionProps, - PgStacApiLambda, - PgStacDatabase, - StacIngestor, - TitilerPgstacApiLambda, - StacBrowser, - StactoolsItemGenerator, - StacLoader, -} from "eoapi-cdk"; -import { readFileSync } from "fs"; -import { load } from "js-yaml"; -import { DpsStacItemGenerator } from "./constructs/DpsStacItemGenerator"; - -export class PgStacInfra extends Stack { - constructor(scope: Construct, id: string, props: Props) { - super(scope, id, props); - - const stack = Stack.of(this); - - const { - vpc, - stage, - type, - version, - certificateArn, - webAclArn, - pgstacDbConfig, - titilerPgstacConfig, - stacApiConfig, - stacBrowserConfig, - ingestorConfig, - loggingBucketArn, - dpsStacItemGenConfig, - addStactoolsItemGenerator, - } = props; - - // Pgstac Database - const pgstacDb = new PgStacDatabase(this, "pgstac-db", { - vpc, - allowMajorVersionUpgrade: true, - engine: rds.DatabaseInstanceEngine.postgres({ - version: rds.PostgresEngineVersion.VER_17, - }), - vpcSubnets: { - subnetType: pgstacDbConfig.subnetPublic - ? ec2.SubnetType.PUBLIC - : ec2.SubnetType.PRIVATE_ISOLATED, - }, - allocatedStorage: pgstacDbConfig.allocatedStorage, - instanceType: pgstacDbConfig.instanceType, - addPgbouncer: true, - addPatchManager: false, - pgstacVersion: pgstacDbConfig.pgstacVersion, - customResourceProperties: { - context: true, - update_collection_extent: true, - use_queue: true, - }, - bootstrapperLambdaFunctionOptions: { timeout: Duration.minutes(15) }, - parameters: { shared_preload_libraries: "pg_cron" }, - }); - if (pgstacDb.pgbouncerInstanceId) { - new ssm.StringParameter(this, "pgbouncer-instance-id-param", { - parameterName: `/maap-eoapi/${stage}/${type}/pgbouncer-instance-id`, - stringValue: pgstacDb.pgbouncerInstanceId, - description: `PgBouncer EC2 instance ID for MAAP eoAPI ${type} stack (${stage})`, - }); - } - - const apiSubnetSelection: ec2.SubnetSelection = { - subnetType: pgstacDbConfig.subnetPublic - ? ec2.SubnetType.PUBLIC - : ec2.SubnetType.PRIVATE_WITH_EGRESS, - }; - - const transactionsConfig = stacApiConfig.transactions; - const catalogsConfig = stacApiConfig.catalogs ?? { enabled: true }; - const catalogsEnabled = catalogsConfig.enabled !== false; - const catalogTransactionsConfig = catalogsConfig.transactions; - if (catalogTransactionsConfig && !catalogsEnabled) { - throw new Error("STAC catalog transactions require catalogs to be enabled"); - } - - if (transactionsConfig && transactionsConfig.authMode !== "basic") { - throw new Error( - `Unsupported STAC collection transaction auth mode: ${transactionsConfig.authMode}`, - ); - } - if (catalogTransactionsConfig && catalogTransactionsConfig.authMode !== "basic") { - throw new Error( - `Unsupported STAC catalog transaction auth mode: ${catalogTransactionsConfig.authMode}`, - ); - } - if ( - transactionsConfig && - catalogTransactionsConfig && - transactionsConfig.authSecretArn !== catalogTransactionsConfig.authSecretArn - ) { - throw new Error( - "STAC collection and catalog transactions must use the same auth secret ARN", - ); - } - - const writeTransactionsConfig = transactionsConfig ?? catalogTransactionsConfig; - const transactionAuthSecret = writeTransactionsConfig - ? writeTransactionsConfig.authSecretArn - ? secretsmanager.Secret.fromSecretCompleteArn( - this, - "stac-collection-transaction-auth-secret", - writeTransactionsConfig.authSecretArn, - ) - : new secretsmanager.Secret( - this, - "stac-collection-transaction-auth-secret", - { - description: `Basic auth secret for MAAP ${type} STAC collection transactions (${stage})`, - secretName: `/maap-eoapi/${stage}/${type}/stac-collection-transaction-basic-auth`, - generateSecretString: { - secretStringTemplate: JSON.stringify({ - username: `maap-${type}-stac-writer`, - }), - generateStringKey: "password", - excludePunctuation: true, - }, - }, - ) - : undefined; - - const stacEnabledExtensions = [ - "query", - "sort", - "fields", - "filter", - "free_text", - "pagination", - "collection_search", - ...(catalogsEnabled ? ["catalogs"] : []), - ...(transactionsConfig ? ["collection_transaction"] : []), - ...(catalogTransactionsConfig ? ["catalog_transaction"] : []), - ]; - - const stacApiEnv: Record = { - STAC_FASTAPI_TITLE: `MAAP ${type} STAC API (${stage})`, - STAC_FASTAPI_LANDING_ID: `maap-${type}-stac-api-${stage}`, - STAC_FASTAPI_DESCRIPTION: `The ${type} STAC API for the [MAAP project](https://maap-project.org)`, - STAC_FASTAPI_VERSION: version, - ENABLED_EXTENSIONS: stacEnabledExtensions.join(","), - ENABLE_CATALOGS_EXTENSION: catalogsEnabled ? "true" : "false", - HIDE_ALTERNATE_PARENTS: catalogsConfig.hideAlternateParents ? "true" : "false", - ...(writeTransactionsConfig - ? { - MAAP_TRANSACTION_AUTH_MODE: writeTransactionsConfig.authMode, - MAAP_TRANSACTION_AUTH_SECRET_ARN: transactionAuthSecret!.secretArn, - } - : {}), - }; - - const stacApiLambdaOptions: CustomLambdaFunctionProps = { - code: lambda.Code.fromDockerBuild(__dirname, { - file: "dockerfiles/Dockerfile.stac", - targetStage: "lambda", - buildArgs: { PYTHON_VERSION: "3.12" }, - }), - handler: "eoapi.stac.handler.handler", - }; - - // STAC API - const stacApiLambda = new PgStacApiLambda(this, "pgstac-api", { - apiEnv: stacApiEnv, - vpc, - db: pgstacDb.connectionTarget, - dbSecret: pgstacDb.pgstacSecret, - subnetSelection: apiSubnetSelection, - stacApiDomainName: - stacApiConfig.customDomainName && certificateArn - ? new apigatewayv2.DomainName(this, "stac-api-domain-name", { - domainName: stacApiConfig.customDomainName, - certificate: acm.Certificate.fromCertificateArn( - this, - "stacApiCustomDomainNameCertificate", - certificateArn, - ), - }) - : undefined, - enableSnapStart: true, - lambdaFunctionOptions: stacApiLambdaOptions, - }); - - stacApiLambda.lambdaFunction.connections.allowTo( - pgstacDb.connectionTarget, - ec2.Port.tcp(5432), - "allow connections from stac-fastapi-pgstac", - ); - - if (stacApiConfig.integrationApiArn) { - stacApiLambda.lambdaFunction.addPermission("ApiGatewayInvoke", { - principal: new iam.ServicePrincipal("apigateway.amazonaws.com"), - sourceArn: stacApiConfig.integrationApiArn, - }); - } - - if (transactionAuthSecret) { - transactionAuthSecret.grantRead(stacApiLambda.lambdaFunction); - - new ssm.StringParameter(this, "stac-collection-transaction-auth-secret-param", { - parameterName: `/maap-eoapi/${stage}/${type}/stac-collection-transaction-auth-secret-arn`, - stringValue: transactionAuthSecret.secretArn, - description: `Secrets Manager ARN for MAAP ${type} STAC transaction auth (${stage})`, - }); - } - - // titiler-pgstac - const titilerDataAccessRole = iam.Role.fromRoleArn( - this, - "titiler-data-access-role", - titilerPgstacConfig.dataAccessRoleArn, - ); - - const fileContents = readFileSync(titilerPgstacConfig.bucketsPath, "utf8"); - const buckets = load(fileContents) as string[]; - - const titilerPgstacLambdaOptions: CustomLambdaFunctionProps = { - code: lambda.Code.fromDockerBuild(__dirname, { - file: "dockerfiles/Dockerfile.raster", - targetStage: "lambda", - buildArgs: { PYTHON_VERSION: "3.12" }, - }), - handler: "handler.handler", - role: titilerDataAccessRole, - }; - - const titilerPgstacApiEnv: Record = { - NAME: `MAAP titiler pgstac API (${stage})`, - VERSION: version, - DESCRIPTION: "titiler pgstac API for the MAAP STAC system.", - }; - - // Only add mosaic configuration if mosaicHost is provided - if (titilerPgstacConfig.mosaicHost) { - titilerPgstacApiEnv.MOSAIC_BACKEND = "dynamodb://"; - titilerPgstacApiEnv.MOSAIC_HOST = titilerPgstacConfig.mosaicHost; - } - - const titilerPgstacApi = new TitilerPgstacApiLambda( - this, - "titiler-pgstac-api", - { - apiEnv: titilerPgstacApiEnv, - vpc, - db: pgstacDb.connectionTarget, - dbSecret: pgstacDb.pgstacSecret, - subnetSelection: apiSubnetSelection, - buckets: buckets, - titilerPgstacApiDomainName: - titilerPgstacConfig.customDomainName && certificateArn - ? new apigatewayv2.DomainName( - this, - "titiler-pgstac-api-domain-name", - { - domainName: titilerPgstacConfig.customDomainName, - certificate: acm.Certificate.fromCertificateArn( - this, - "titilerPgStacCustomDomainNameCertificate", - certificateArn, - ), - }, - ) - : undefined, - lambdaFunctionOptions: titilerPgstacLambdaOptions, - enableSnapStart: true, - }, - ); - - if (titilerPgstacConfig.mosaicHost) { - // Add dynamodb permissions to the titiler-pgstac Lambda for mosaicjson support - const tableName = titilerPgstacConfig.mosaicHost.split("/", 2)[1]; - - const mosaicPerms = [ - new iam.PolicyStatement({ - actions: ["dynamodb:CreateTable", "dynamodb:DescribeTable"], - resources: [ - `arn:aws:dynamodb:${stack.region}:${stack.account}:table/*`, - ], - }), - new iam.PolicyStatement({ - actions: [ - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:Scan", - "dynamodb:PutItem", - "dynamodb:BatchWriteItem", - ], - resources: [ - `arn:aws:dynamodb:${stack.region}:${stack.account}:table/${tableName}`, - ], - }), - ]; - - mosaicPerms.forEach((permission) => { - titilerPgstacApi.lambdaFunction.addToRolePolicy(permission); - }); - } - - // Configure titiler-pgstac for pgbouncer - titilerPgstacApi.lambdaFunction.connections.allowTo( - pgstacDb.connectionTarget, - ec2.Port.tcp(5432), - "allow connections from titiler", - ); - - // API logging dashboard - - const eoapiDashboard = new cloudwatch.Dashboard(this, "eoAPIDashboard", { - dashboardName: `eoAPI-${stage}-${type}`, - }); - - // widget showing count by application route - const titilerRouteLogWidget = new cloudwatch.LogQueryWidget({ - logGroupNames: [titilerPgstacApi.lambdaFunction.logGroup.logGroupName], - title: "titiler requests by route", - width: 12, - height: 8, - view: cloudwatch.LogQueryVisualizationType.TABLE, - queryLines: [ - "fields @timestamp, @message", - 'filter @message like "Request:"', - 'parse @message \'"route": "*",\' as route', - "stats count(*) as count by route", - "sort count desc", - "limit 20", - ], - }); - - // widget showing count by referer - const titilerRefererAnalysisWidget = new cloudwatch.LogQueryWidget({ - logGroupNames: [titilerPgstacApi.lambdaFunction.logGroup.logGroupName], - title: "titiler requests by request referer", - width: 6, - height: 8, - view: cloudwatch.LogQueryVisualizationType.TABLE, - queryLines: [ - "fields @timestamp, @message", - 'filter @message like "Request:"', - 'parse @message \'"referer": "*"\' as referer', - "stats count(*) as count by referer", - "sort count desc", - "limit 20", - ], - }); - - // widget showing count by scheme/netloc for routes with url parameter - const titilerUrlAnalysisWidget = new cloudwatch.LogQueryWidget({ - logGroupNames: [titilerPgstacApi.lambdaFunction.logGroup.logGroupName], - title: "titiler /cog requests by url scheme and netloc", - width: 6, - height: 8, - view: cloudwatch.LogQueryVisualizationType.TABLE, - queryLines: [ - "fields @timestamp, @message", - 'filter @message like "Request:"', - 'parse @message \'"url_scheme": "*"\' as url_scheme', - 'parse @message \'"url_netloc": "*"\' as url_netloc', - "filter ispresent(url_scheme)", - "stats count(*) as count by url_scheme, url_netloc", - "sort count desc", - "limit 20", - ], - }); - - // widget showing count by collection_id for /collections requests - const titilerCollectionAnalysisWidget = new cloudwatch.LogQueryWidget({ - logGroupNames: [titilerPgstacApi.lambdaFunction.logGroup.logGroupName], - title: "titiler /collections requests by collection id", - width: 6, - height: 8, - view: cloudwatch.LogQueryVisualizationType.TABLE, - queryLines: [ - "fields @timestamp, @message", - 'filter @message like "Request:"', - 'parse @message \'"route": "*"\' as route', - 'filter route like "/collections/"', - "parse @message '\"path_params\": {*}' as path_params", - "stats count(*) as count by path_params.collection_id as collection_id", - "sort count desc", - "limit 20", - ], - }); - - // widget showing count by collection_id for /collections requests - const titilerSearchesAnalysisWidget = new cloudwatch.LogQueryWidget({ - logGroupNames: [titilerPgstacApi.lambdaFunction.logGroup.logGroupName], - title: "titiler /searches requests by search id", - width: 6, - height: 8, - view: cloudwatch.LogQueryVisualizationType.TABLE, - queryLines: [ - "fields @timestamp, @message", - 'filter @message like "Request:"', - 'parse @message \'"route": "*"\' as route', - 'filter route like "/searches/"', - "parse @message '\"path_params\": {*}' as path_params", - "stats count(*) as count by path_params.search_id as search_id", - "sort count desc", - "limit 20", - ], - }); - eoapiDashboard.addWidgets( - titilerRouteLogWidget, - titilerCollectionAnalysisWidget, - titilerSearchesAnalysisWidget, - titilerUrlAnalysisWidget, - titilerRefererAnalysisWidget, - ); - - // STAC Ingestor - if (ingestorConfig) { - const ingestorDataAccessRole = iam.Role.fromRoleArn( - this, - "ingestor-data-access-role", - ingestorConfig.dataAccessRoleArn, - ); - - new StacIngestor(this, "stac-ingestor", { - vpc, - stacUrl: stacApiLambda.url, - dataAccessRole: ingestorDataAccessRole, - stage, - stacDbSecret: pgstacDb.pgstacSecret, - stacDbSecurityGroup: pgstacDb.securityGroup!, - subnetSelection: { - subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, - }, - apiEnv: { - JWKS_URL: ingestorConfig.jwksUrl, - REQUESTER_PAYS: "true", - }, - pgstacVersion: pgstacDbConfig.pgstacVersion, - ingestorDomainNameOptions: - ingestorConfig.domainName && certificateArn - ? { - domainName: ingestorConfig.domainName, - certificate: acm.Certificate.fromCertificateArn( - this, - "ingestorCustomDomainNameCertificate", - certificateArn, - ), - } - : undefined, - }); - } - - const logBucket = s3.Bucket.fromBucketAttributes(this, "LoggingBucket", { - bucketArn: loggingBucketArn, - }); - if (stacBrowserConfig) { - // STAC Browser Infrastructure - const rootPath = "index.html"; - - const stacBrowserBucket = new s3.Bucket(this, "stacBrowserBucket", { - accessControl: s3.BucketAccessControl.PRIVATE, - removalPolicy: RemovalPolicy.DESTROY, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - bucketName: `maap-stac-browser-${stage}`, - enforceSSL: true, - }); - - const stacBrowserOrigin = new cloudfront.Distribution( - this, - "stacBrowserDistro", - { - defaultBehavior: { origin: new origins.S3Origin(stacBrowserBucket) }, - defaultRootObject: rootPath, - domainNames: [stacBrowserConfig.customDomainName], - certificate: acm.Certificate.fromCertificateArn( - this, - "stacBrowserCustomDomainNameCertificate", - stacBrowserConfig.certificateArn, - ), - enableLogging: true, - logBucket, - logFilePrefix: `stac-browser-${type}`, - errorResponses: [ - { - httpStatus: 403, - responseHttpStatus: 200, - responsePagePath: `/${rootPath}`, - ttl: Duration.seconds(0), - }, - { - httpStatus: 404, - responseHttpStatus: 200, - responsePagePath: `/${rootPath}`, - ttl: Duration.seconds(0), - }, - ], - webAclId: webAclArn, - }, - ); - - new StacBrowser(this, "stac-browser", { - bucketArn: stacBrowserBucket.bucketArn, - stacCatalogUrl: stacApiConfig.customDomainName - ? stacApiConfig.customDomainName.startsWith("https://") - ? stacApiConfig.customDomainName - : `https://${stacApiConfig.customDomainName}/` - : stacApiLambda.url, - githubRepoTag: stacBrowserConfig.repoTag, - websiteIndexDocument: rootPath, - }); - - const accountId = Aws.ACCOUNT_ID; - const distributionArn = `arn:aws:cloudfront::${accountId}:distribution/${stacBrowserOrigin.distributionId}`; - - stacBrowserBucket.addToResourcePolicy( - new iam.PolicyStatement({ - sid: "AllowCloudFrontServicePrincipal", - effect: iam.Effect.ALLOW, - actions: ["s3:GetObject"], - principals: [new iam.ServicePrincipal("cloudfront.amazonaws.com")], - resources: [stacBrowserBucket.arnForObjects("*")], - conditions: { - StringEquals: { - "aws:SourceArn": distributionArn, - }, - }, - }), - ); - - logBucket.addToResourcePolicy( - new iam.PolicyStatement({ - sid: "AllowCloudFrontServicePrincipal", - effect: iam.Effect.ALLOW, - actions: ["s3:PutObject"], - resources: [logBucket.arnForObjects("AWSLogs/*")], - principals: [new iam.ServicePrincipal("cloudfront.amazonaws.com")], - conditions: { - StringEquals: { - "aws:SourceArn": distributionArn, - }, - }, - }), - ); - } - - // item loader - const stacLoader = new StacLoader(this, "stac-item-loader", { - pgstacDb, - vpc: vpc, - subnetSelection: apiSubnetSelection, - batchSize: 500, - lambdaTimeoutSeconds: 300, - maxBatchingWindowMinutes: 5, - environment: { - CREATE_COLLECTIONS_IF_MISSING: "TRUE", - }, - }); - - pgstacDb.pgstacSecret.grantRead(stacLoader.lambdaFunction); - - stacLoader.lambdaFunction.connections.allowTo( - pgstacDb.connectionTarget, - ec2.Port.tcp(5432), - "allow connections from stac-item-loader", - ); - - // item generators - if (addStactoolsItemGenerator) { - const stactoolsItemGenerator = new StactoolsItemGenerator( - this, - "stactools-item-generator", - { - itemLoadTopicArn: stacLoader.topic.topicArn, - vpc, - subnetSelection: apiSubnetSelection, - }, - ); - stactoolsItemGenerator.lambdaFunction.addToRolePolicy( - new iam.PolicyStatement({ - actions: ["s3:GetObject"], - resources: ["arn:aws:s3:::*/*"], - }), - ); - stacLoader.topic.grantPublish(stactoolsItemGenerator.lambdaFunction); - } - - if (dpsStacItemGenConfig) { - const dpsStacItemGenerator = new DpsStacItemGenerator( - this, - "dps-item-generator", - { - itemLoadTopicArn: stacLoader.topic.topicArn, - roleArn: dpsStacItemGenConfig.itemGenRoleArn, - inboundTopicArns: dpsStacItemGenConfig.inboundTopicArns, - userStacCollectionIdRegistry: - dpsStacItemGenConfig.userStacCollectionIdRegistry, - vpc, - subnetSelection: apiSubnetSelection, - stage, - }, - ); - - stacLoader.topic.grantPublish(dpsStacItemGenerator.lambdaFunction); - } - } -} - -export interface Props extends StackProps { - vpc: ec2.Vpc; - - /** - * Stage this stack. Used for naming resources. - */ - stage: string; - - /** - * Type of this deployment, e.g. "public", "internal" - */ - type: string; - - /** - * Version of this stack. Used to correlate codebase versions - * to services running. - */ - version: string; - - /** - * ARN of ACM certificate to use for eoAPI custom domains - * Example: "arn:aws:acm:us-west-2:123456789012:certificate/12345678-1234-1234-1234-123456789012" - */ - certificateArn?: string | undefined; - - /** - * ARN of WAF Web ACL to use for eoAPI custom domains - * Example: "arn:aws:wafv2:us-west-2:123456789012:webacl/12345678-1234-1234-1234-123456789012" - */ - webAclArn: string; - - /** - * ARN for S3 bucket for logging - */ - loggingBucketArn: string; - - pgstacDbConfig: { - /** - * RDS Instance type - */ - instanceType: ec2.InstanceType; - - /** - * Flag to control whether database should be deployed into a - * public subnet. - */ - subnetPublic: boolean; - - /** - * allocated storage for pgstac database - */ - allocatedStorage: number; - - /** - * version of pgstac to install on the database - */ - pgstacVersion: string; - }; - - titilerPgstacConfig: { - /** - * mosaicjson dynamodb host for titiler in form of aws-region/table-name - */ - mosaicHost?: string | undefined; - - /** - * yaml file containing the list of buckets the titiler lambda should be granted access to - */ - bucketsPath: string; - - /** - * ARN of IAM role that will be assumed by the titiler Lambda - */ - dataAccessRoleArn: string; - - /** - * Domain name to use for titiler pgstac api. If defined, a new custom domain name will be created. - * Example: "titiler-pgstac-api.dit.maap-project.org" - */ - customDomainName?: string | undefined; - }; - - stacApiConfig: { - /** - * Domain name to use for stac api. If defined, a new CDN will be created. - * Example: "stac-api.dit.maap-project.org"" - */ - customDomainName?: string; - - /** - * STAC API api gateway source ARN to be granted STAC API lambda invoke permission. - */ - integrationApiArn?: string; - - /** - * Optional collection transaction support for the STAC API. - * When omitted, collection write routes stay disabled. - */ - transactions?: { - authMode: "basic" | "jwt"; - authSecretArn?: string; - }; - - /** - * Optional multi-tenant catalog support for the STAC API. - * When omitted, read-only catalog routes are enabled. - */ - catalogs?: { - enabled: boolean; - hideAlternateParents?: boolean; - transactions?: { - authMode: "basic" | "jwt"; - authSecretArn?: string; - }; - }; - }; - - /** - * Configuration for the STAC Browser - */ - stacBrowserConfig?: { - /** - * Tag of the stac-browser repo from https://github.com/radiantearth/stac-browser - * Example: "v3.2.0" - */ - repoTag: string; - - /** - * Domain name for use in cloudfront distribution for stac-browser - * Example: "stac-browser.maap-project.org" - */ - customDomainName: string; - - /** - * ARN of ACM certificate to use for Cloudfront Distribution (Must be us-east-1). - * Example: "arn:aws:acm:us-west-2:123456789012:certificate/12345678-1234-1234-1234-123456789012" - */ - certificateArn: string; - }; - - // === OPTIONAL COMPONENTS === - /** - * Configuration for the STAC Ingestor. If not provided, STAC Ingestor will not be created. - */ - ingestorConfig?: { - /** - * URL of JWKS endpoint, provided as output from ASDI-Auth. - * - * Example: "https://cognito-idp.{region}.amazonaws.com/{region}_{userpool_id}/.well-known/jwks.json" - */ - jwksUrl: string; - /** - * ARN of IAM role that will be assumed by the STAC Ingestor. - */ - dataAccessRoleArn: string; - - /** - * Domain name to use for CDN. If defined, a new CDN will be created - * Example: "stac.maap.xyz" - */ - domainName?: string | undefined; - - /** - * Where userdata.yaml is found. - */ - userDataPath: string; - }; - dpsStacItemGenConfig?: { - itemGenRoleArn: string; - inboundTopicArns?: string[]; - userStacCollectionIdRegistry?: Record; - }; - addStactoolsItemGenerator?: boolean | undefined; -} diff --git a/cdk/app.ts b/cdk/app.ts deleted file mode 100644 index 91a1c3a..0000000 --- a/cdk/app.ts +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env node -import "source-map-support/register"; -import * as cdk from "aws-cdk-lib"; - -import { Vpc } from "./Vpc"; -import { Config } from "./config"; -import { PgStacInfra } from "./PgStacInfra"; -import { MaapEoapiCommon } from "./MaapEoapiCommon"; -import { PatchManagerStack } from "./PatchManager"; - -const { - buildStackName, - certificateArn, - dbAllocatedStorage, - dbInstanceType, - ingestorDataAccessRoleArn, - ingestorDomainName, - jwksUrl, - mosaicHost, - pgstacVersion, - stacApiCustomDomainName, - stacApiIntegrationApiArn, - stacBrowserCertificateArn, - stacBrowserCustomDomainName, - stacBrowserRepoTag, - stage, - tags, - titilerDataAccessRoleArn, - titilerPgStacApiCustomDomainName, - userStacCollectionIdRegistry, - userStacInboundTopicArns, - userStacItemGenRoleArn, - userStacCatalogs, - userStacCollectionTransactions, - userStacStacApiCustomDomainName, - userStacTitilerPgStacApiCustomDomainName, - version, - webAclArn, -} = new Config(); - -export const app = new cdk.App({}); - -const { vpc } = new Vpc(app, buildStackName("vpc"), { - terminationProtection: false, - tags, - natGatewayCount: stage === "prod" ? undefined : 1, -}); - -// Create common resources to be shared by pgSTAC and userSTAC stacks -const common = new MaapEoapiCommon(app, buildStackName("common"), { - tags, - stage, - terminationProtection: false, -}); - -const coreInfrastructure = new PgStacInfra(app, buildStackName("pgSTAC"), { - vpc, - tags, - stage, - type: "public", - version, - certificateArn, - webAclArn, - loggingBucketArn: common.loggingBucket.bucketArn, - pgstacDbConfig: { - instanceType: dbInstanceType, - pgstacVersion: pgstacVersion, - allocatedStorage: dbAllocatedStorage, - subnetPublic: false, - }, - stacApiConfig: { - customDomainName: stacApiCustomDomainName, - integrationApiArn: stacApiIntegrationApiArn, - catalogs: { enabled: true }, - }, - titilerPgstacConfig: { - mosaicHost, - bucketsPath: "./titiler_buckets.yaml", - customDomainName: titilerPgStacApiCustomDomainName, - dataAccessRoleArn: titilerDataAccessRoleArn, - }, - stacBrowserConfig: { - repoTag: stacBrowserRepoTag, - customDomainName: stacBrowserCustomDomainName, - certificateArn: stacBrowserCertificateArn, - }, - ingestorConfig: { - jwksUrl, - dataAccessRoleArn: ingestorDataAccessRoleArn, - domainName: ingestorDomainName, - userDataPath: "./userdata.yaml" - }, - addStactoolsItemGenerator: true, - terminationProtection: false, -}); - -const userInfrastructure = new PgStacInfra(app, buildStackName("userSTAC"), { - vpc, - tags, - stage, - type: "internal", - version, - certificateArn, - webAclArn, - loggingBucketArn: common.loggingBucket.bucketArn, - pgstacDbConfig: { - instanceType: dbInstanceType, - pgstacVersion: pgstacVersion, - allocatedStorage: dbAllocatedStorage, - subnetPublic: false, - }, - stacApiConfig: { - customDomainName: userStacStacApiCustomDomainName, - catalogs: userStacCatalogs, - transactions: userStacCollectionTransactions, - }, - titilerPgstacConfig: { - mosaicHost, - bucketsPath: "./titiler_buckets.yaml", - customDomainName: userStacTitilerPgStacApiCustomDomainName, - dataAccessRoleArn: titilerDataAccessRoleArn, - }, - // stacBrowserConfig: { - // repoTag: stacBrowserRepoTag, - // customDomainName: stacBrowserCustomDomainName, - // certificateArn: stacBrowserCertificateArn, - // }, - addStactoolsItemGenerator: false, - ...(userStacItemGenRoleArn && { - dpsStacItemGenConfig: { - itemGenRoleArn: userStacItemGenRoleArn, - inboundTopicArns: userStacInboundTopicArns, - userStacCollectionIdRegistry, - }, - }), - terminationProtection: false, -}); - -const patchManager = new PatchManagerStack(app, buildStackName("patch-manager"), { - pgbouncerParamNames: [ - `/maap-eoapi/${stage}/public/pgbouncer-instance-id`, - `/maap-eoapi/${stage}/internal/pgbouncer-instance-id`, - ], - terminationProtection: false, -}); -patchManager.addDependency(coreInfrastructure); -patchManager.addDependency(userInfrastructure); diff --git a/cdk/config.py b/cdk/config.py index 5131705..12824ad 100644 --- a/cdk/config.py +++ b/cdk/config.py @@ -40,6 +40,7 @@ class StacApiConfig: custom_domain_name: Optional[str] = None integration_api_arn: Optional[str] = None transactions: Optional[CollectionTransactionsConfig] = None + catalogs: Optional["StacCatalogsConfig"] = None @dataclass @@ -64,6 +65,13 @@ class DpsStacItemGenConfig: user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None +@dataclass +class StacCatalogsConfig: + enabled: bool + hide_alternate_parents: Optional[bool] = None + transactions: Optional[CollectionTransactionsConfig] = None + + class Config(BaseSettings): model_config = SettingsConfigDict( env_ignore_empty=True, @@ -110,6 +118,12 @@ class Config(BaseSettings): user_stac_collection_transactions_auth_mode: Optional[str] = None user_stac_collection_transactions_auth_secret_arn: Optional[str] = None user_stac_collection_transactions: Optional[CollectionTransactionsConfig] = None + user_stac_catalogs_enabled: Optional[bool] = None + user_stac_catalogs_hide_alternate_parents: Optional[bool] = None + user_stac_catalog_transactions_enabled: Optional[bool] = None + user_stac_catalog_transactions_auth_mode: Optional[str] = None + user_stac_catalog_transactions_auth_secret_arn: Optional[str] = None + user_stac_catalogs: Optional[StacCatalogsConfig] = None @field_validator("db_instance_type", mode="before") @classmethod @@ -121,19 +135,88 @@ def parse_instance_type(cls, v: object) -> ec2.InstanceType: except Exception as e: raise ValueError(f"Invalid DB_INSTANCE_TYPE: {v!r}") from e + @field_validator( + "user_stac_collection_transactions_enabled", + "user_stac_catalogs_enabled", + "user_stac_catalogs_hide_alternate_parents", + "user_stac_catalog_transactions_enabled", + mode="before", + ) + @classmethod + def parse_optional_bool_env(cls, v: object) -> Optional[bool]: + if v is None: + return None + if isinstance(v, bool): + return v + + normalized = str(v).strip().lower() + if not normalized: + return None + if normalized == "true": + return True + if normalized == "false": + return False + + raise ValueError( + f"Invalid boolean value: {v!r}. Expected 'true' or 'false'." + ) + @model_validator(mode="after") def assemble_collection_transactions(self) -> Config: if self.user_stac_collection_transactions_enabled is not True: - return self - if not self.user_stac_collection_transactions_auth_mode: + self.user_stac_collection_transactions = None + else: + if not self.user_stac_collection_transactions_auth_mode: + raise ValueError( + "Must provide USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE " + "when USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true" + ) + if self.user_stac_collection_transactions_auth_mode != "basic": + raise ValueError( + "Unsupported USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE: " + f"{self.user_stac_collection_transactions_auth_mode}. " + 'Expected "basic".' + ) + self.user_stac_collection_transactions = CollectionTransactionsConfig( + auth_mode=self.user_stac_collection_transactions_auth_mode, + auth_secret_arn=self.user_stac_collection_transactions_auth_secret_arn, + ) + + catalogs_enabled = self.user_stac_catalogs_enabled + if catalogs_enabled is None: + catalogs_enabled = True + + catalog_transactions_enabled = self.user_stac_catalog_transactions_enabled is True + if catalog_transactions_enabled and not catalogs_enabled: raise ValueError( - "Must provide USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE " - "when USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true" + "USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true requires " + "USER_STAC_CATALOGS_ENABLED=true" + ) + + catalogs_transactions: Optional[CollectionTransactionsConfig] = None + if catalog_transactions_enabled: + if not self.user_stac_catalog_transactions_auth_mode: + raise ValueError( + "Must provide USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE when " + "USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true" + ) + if self.user_stac_catalog_transactions_auth_mode != "basic": + raise ValueError( + "Unsupported USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE: " + f"{self.user_stac_catalog_transactions_auth_mode}. " + 'Expected "basic".' + ) + catalogs_transactions = CollectionTransactionsConfig( + auth_mode=self.user_stac_catalog_transactions_auth_mode, + auth_secret_arn=self.user_stac_catalog_transactions_auth_secret_arn, ) - self.user_stac_collection_transactions = CollectionTransactionsConfig( - auth_mode=self.user_stac_collection_transactions_auth_mode, - auth_secret_arn=self.user_stac_collection_transactions_auth_secret_arn, + + self.user_stac_catalogs = StacCatalogsConfig( + enabled=catalogs_enabled, + hide_alternate_parents=self.user_stac_catalogs_hide_alternate_parents, + transactions=catalogs_transactions, ) + return self @computed_field # type: ignore[prop-decorator] @@ -143,3 +226,61 @@ def tags(self) -> dict[str, str]: def build_stack_name(self, service_id: str) -> str: return f"MAAP-STAC-{self.stage}-{service_id}" + + def pgstac_db(self) -> PgStacDbConfig: + return PgStacDbConfig( + instance_type=self.db_instance_type, + pgstac_version=self.pgstac_version, + allocated_storage=self.db_allocated_storage, + subnet_public=False, + ) + + def stac_api(self, *, user_stac: bool = False) -> StacApiConfig: + if user_stac: + return StacApiConfig( + custom_domain_name=self.user_stac_stac_api_custom_domain_name, + transactions=self.user_stac_collection_transactions, + catalogs=self.user_stac_catalogs, + ) + + return StacApiConfig( + custom_domain_name=self.stac_api_custom_domain_name, + integration_api_arn=self.stac_api_integration_api_arn, + catalogs=StacCatalogsConfig(enabled=True), + ) + + def titiler_pgstac(self, *, user_stac: bool = False) -> TitilerPgstacConfig: + return TitilerPgstacConfig( + mosaic_host=self.mosaic_host, + buckets_path="./titiler_buckets.yaml", + custom_domain_name=( + self.user_stac_titiler_pgstac_api_custom_domain_name + if user_stac + else self.titiler_pg_stac_api_custom_domain_name + ), + data_access_role_arn=self.titiler_data_access_role_arn, + ) + + def stac_browser(self) -> StacBrowserConfig: + return StacBrowserConfig( + repo_tag=self.stac_browser_repo_tag, + custom_domain_name=self.stac_browser_custom_domain_name, + certificate_arn=self.stac_browser_certificate_arn, + ) + + def ingestor(self) -> IngestorConfig: + return IngestorConfig( + jwks_url=self.jwks_url, + data_access_role_arn=self.ingestor_data_access_role_arn, + domain_name=self.ingestor_domain_name, + user_data_path="./userdata.yaml", + ) + + def dps_stac_item_gen(self) -> Optional[DpsStacItemGenConfig]: + if not self.user_stac_item_gen_role_arn: + return None + return DpsStacItemGenConfig( + item_gen_role_arn=self.user_stac_item_gen_role_arn, + inbound_topic_arns=self.user_stac_inbound_topic_arns, + user_stac_collection_id_registry=self.user_stac_collection_id_registry, + ) diff --git a/cdk/config.ts b/cdk/config.ts deleted file mode 100644 index 706ba53..0000000 --- a/cdk/config.ts +++ /dev/null @@ -1,286 +0,0 @@ -import * as aws_ec2 from "aws-cdk-lib/aws-ec2"; - -export interface CollectionTransactionsConfig { - authMode: "basic" | "jwt"; - authSecretArn?: string; -} - -export interface StacCatalogsConfig { - enabled: boolean; - hideAlternateParents?: boolean; - transactions?: CollectionTransactionsConfig; -} - -function parseOptionalBooleanEnv(name: string): boolean | undefined { - const value = process.env[name]; - if (value === undefined || value === "") { - return undefined; - } - - const normalizedValue = value.trim().toLowerCase(); - if (normalizedValue === "true") { - return true; - } - - if (normalizedValue === "false") { - return false; - } - - throw new Error(`Invalid ${name}: ${value}. Expected "true" or "false".`); -} - -export class Config { - readonly stage: string; - readonly version: string; - readonly dbInstanceType: aws_ec2.InstanceType; - readonly tags: Record; - readonly jwksUrl: string; - readonly titilerDataAccessRoleArn: string; - readonly ingestorDataAccessRoleArn: string; - readonly stacApiIntegrationApiArn: string; - readonly dbAllocatedStorage: number; - readonly mosaicHost: string; - readonly certificateArn: string | undefined; - readonly ingestorDomainName: string | undefined; - readonly stacApiCustomDomainName: string; - readonly titilerPgStacApiCustomDomainName: string | undefined; - readonly stacBrowserRepoTag: string; - readonly stacBrowserCustomDomainName: string; - readonly stacBrowserCertificateArn: string; - readonly pgstacVersion: string; - readonly webAclArn: string; - readonly userStacItemGenRoleArn: string; - readonly userStacInboundTopicArns: string[] | undefined; - readonly userStacCollectionIdRegistry: Record | undefined; - readonly userStacStacApiCustomDomainName: string | undefined; - readonly userStacTitilerPgStacApiCustomDomainName: string | undefined; - readonly userStacCollectionTransactions: - | CollectionTransactionsConfig - | undefined; - readonly userStacCatalogs: StacCatalogsConfig; - - constructor() { - const requiredVariables = [ - { name: "STAGE", value: process.env.STAGE }, - { name: "DB_INSTANCE_TYPE", value: process.env.DB_INSTANCE_TYPE }, - { name: "JWKS_URL", value: process.env.JWKS_URL }, - { - name: "TITILER_DATA_ACCESS_ROLE_ARN", - value: process.env.TITILER_DATA_ACCESS_ROLE_ARN, - }, - { - name: "INGESTOR_DATA_ACCESS_ROLE_ARN", - value: process.env.INGESTOR_DATA_ACCESS_ROLE_ARN, - }, - { - name: "STAC_API_INTEGRATION_API_ARN", - value: process.env.STAC_API_INTEGRATION_API_ARN, - }, - { name: "DB_ALLOCATED_STORAGE", value: process.env.DB_ALLOCATED_STORAGE }, - { name: "MOSAIC_HOST", value: process.env.MOSAIC_HOST }, - { - name: "STAC_BROWSER_REPO_TAG", - value: process.env.STAC_BROWSER_REPO_TAG, - }, - { - name: "STAC_BROWSER_CUSTOM_DOMAIN_NAME", - value: process.env.STAC_BROWSER_CUSTOM_DOMAIN_NAME, - }, - { - name: "STAC_BROWSER_CERTIFICATE_ARN", - value: process.env.STAC_BROWSER_CERTIFICATE_ARN, - }, - { - name: "STAC_API_CUSTOM_DOMAIN_NAME", - value: process.env.STAC_API_CUSTOM_DOMAIN_NAME, - }, - { - name: "PGSTAC_VERSION", - value: process.env.PGSTAC_VERSION, - }, - { - name: "WEB_ACL_ARN", - value: process.env.WEB_ACL_ARN, - }, - ]; - - for (const variable of requiredVariables) { - if (!variable.value) { - throw new Error(`Must provide ${variable.name}`); - } - } - - this.stage = process.env.STAGE!; - - this.jwksUrl = process.env.JWKS_URL!; - this.titilerDataAccessRoleArn = process.env.TITILER_DATA_ACCESS_ROLE_ARN!; - this.ingestorDataAccessRoleArn = process.env.INGESTOR_DATA_ACCESS_ROLE_ARN!; - this.stacApiIntegrationApiArn = process.env.STAC_API_INTEGRATION_API_ARN!; - - try { - this.dbInstanceType = new aws_ec2.InstanceType( - process.env.DB_INSTANCE_TYPE!, - ); - } catch (error) { - throw new Error( - `Invalid DB_INSTANCE_TYPE: ${process.env.DB_INSTANCE_TYPE!}. Error: ${error}`, - ); - } - - this.dbAllocatedStorage = Number(process.env.DB_ALLOCATED_STORAGE!); - this.mosaicHost = process.env.MOSAIC_HOST!; - this.stacBrowserRepoTag = process.env.STAC_BROWSER_REPO_TAG!; - this.stacBrowserCustomDomainName = - process.env.STAC_BROWSER_CUSTOM_DOMAIN_NAME!; - this.stacBrowserCertificateArn = process.env.STAC_BROWSER_CERTIFICATE_ARN!; - this.stacApiCustomDomainName = process.env.STAC_API_CUSTOM_DOMAIN_NAME!; - - this.version = process.env.npm_package_version!; - this.tags = { - project: "MAAP", - version: this.version, - stage: this.stage, - }; - - this.certificateArn = process.env.CERTIFICATE_ARN; - this.ingestorDomainName = process.env.INGESTOR_DOMAIN_NAME; - this.titilerPgStacApiCustomDomainName = - process.env.TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME; - this.pgstacVersion = process.env.PGSTAC_VERSION!; - this.webAclArn = process.env.WEB_ACL_ARN!; - this.userStacItemGenRoleArn = process.env.USER_STAC_ITEM_GEN_ROLE_ARN!; - this.userStacStacApiCustomDomainName = - process.env.USER_STAC_STAC_API_CUSTOM_DOMAIN_NAME; - this.userStacTitilerPgStacApiCustomDomainName = - process.env.USER_STAC_TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME; - this.userStacCollectionTransactions = - this.parseUserStacCollectionTransactions(); - this.userStacCatalogs = this.parseUserStacCatalogs(); - - if (process.env.USER_STAC_INBOUND_TOPIC_ARNS) { - try { - this.userStacInboundTopicArns = JSON.parse( - process.env.USER_STAC_INBOUND_TOPIC_ARNS, - ) as string[]; - } catch (error) { - throw new Error( - `Invalid JSON format for USER_STAC_INBOUND_TOPIC_ARNS: ${error}. ` + - `Expected format: ["arn:aws:sns:us-west-2:123456789012:topic-name", ...]`, - ); - } - } else { - this.userStacInboundTopicArns = undefined; - } - - if (process.env.USER_STAC_COLLECTION_ID_REGISTRY) { - try { - this.userStacCollectionIdRegistry = JSON.parse( - process.env.USER_STAC_COLLECTION_ID_REGISTRY, - ) as Record; - } catch (error) { - throw new Error( - `Invalid JSON format for USER_STAC_COLLECTION_ID_REGISTRY: ${error}. ` + - `Expected format: {"collection-id": ["user1", "user2"]}`, - ); - } - } else { - this.userStacCollectionIdRegistry = undefined; - } - } - - /** - * Helper to generate id of stack - * @param serviceId Identifier of service - * @returns Full id of stack - */ - buildStackName = (serviceId: string): string => - `MAAP-STAC-${this.stage}-${serviceId}`; - - private parseUserStacCollectionTransactions(): - | CollectionTransactionsConfig - | undefined { - const enabled = parseOptionalBooleanEnv( - "USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", - ); - - if (enabled !== true) { - return undefined; - } - - const authMode = process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE; - if (!authMode) { - throw new Error( - "Must provide USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE when USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true", - ); - } - - if (authMode !== "basic") { - throw new Error( - `Unsupported USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE: ${authMode}. Expected \"basic\".`, - ); - } - - const authSecretArn = - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN; - - return authSecretArn - ? { - authMode, - authSecretArn, - } - : { - authMode, - }; - } - - private parseUserStacCatalogs(): StacCatalogsConfig { - const enabled = parseOptionalBooleanEnv("USER_STAC_CATALOGS_ENABLED") ?? true; - const hideAlternateParents = parseOptionalBooleanEnv( - "USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS", - ); - const transactionsEnabled = parseOptionalBooleanEnv( - "USER_STAC_CATALOG_TRANSACTIONS_ENABLED", - ); - - if (transactionsEnabled === true && !enabled) { - throw new Error( - "USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true requires USER_STAC_CATALOGS_ENABLED=true", - ); - } - - if (transactionsEnabled !== true) { - return { - enabled, - ...(hideAlternateParents !== undefined && { hideAlternateParents }), - }; - } - - const authMode = process.env.USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE; - if (!authMode) { - throw new Error( - "Must provide USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE when USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true", - ); - } - - if (authMode !== "basic") { - throw new Error( - `Unsupported USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE: ${authMode}. Expected \"basic\".`, - ); - } - - const authSecretArn = process.env.USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN; - - return { - enabled, - ...(hideAlternateParents !== undefined && { hideAlternateParents }), - transactions: authSecretArn - ? { - authMode, - authSecretArn, - } - : { - authMode, - }, - }; - } -} diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index 64e30ff..23e4f77 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -33,6 +33,7 @@ StacBrowserConfig, IngestorConfig, DpsStacItemGenConfig, + StacCatalogsConfig, ) from constructs import Construct @@ -126,19 +127,43 @@ def __init__( # ── Collection transactions config ───────────────────────────────── transactions_config = stac_api_config.transactions + catalogs_config = stac_api_config.catalogs or StacCatalogsConfig(enabled=True) + catalogs_enabled = catalogs_config.enabled is not False + catalog_transactions_config = catalogs_config.transactions + + if catalog_transactions_config and not catalogs_enabled: + raise ValueError("STAC catalog transactions require catalogs to be enabled") + if transactions_config and transactions_config.auth_mode != "basic": raise ValueError( f"Unsupported STAC collection transaction auth mode: " f"{transactions_config.auth_mode}" ) + if catalog_transactions_config and catalog_transactions_config.auth_mode != "basic": + raise ValueError( + f"Unsupported STAC catalog transaction auth mode: " + f"{catalog_transactions_config.auth_mode}" + ) + if ( + transactions_config + and catalog_transactions_config + and transactions_config.auth_secret_arn + != catalog_transactions_config.auth_secret_arn + ): + raise ValueError( + "STAC collection and catalog transactions must use the same auth " + "secret ARN" + ) - if transactions_config: - if transactions_config.auth_secret_arn: + write_transactions_config = transactions_config or catalog_transactions_config + + if write_transactions_config: + if write_transactions_config.auth_secret_arn: transaction_auth_secret: Optional[secretsmanager.ISecret] = ( secretsmanager.Secret.from_secret_complete_arn( self, "stac-collection-transaction-auth-secret", - transactions_config.auth_secret_arn, + write_transactions_config.auth_secret_arn, ) ) else: @@ -172,9 +197,10 @@ def __init__( "free_text", "pagination", "collection_search", + *(["catalogs"] if catalogs_enabled else []), + *(["collection_transaction"] if transactions_config else []), + *(["catalog_transaction"] if catalog_transactions_config else []), ] - if transactions_config: - stac_enabled_extensions.append("collection_transaction") stac_api_env: dict[str, str] = { "STAC_FASTAPI_TITLE": f"MAAP {type} STAC API ({stage})", @@ -184,12 +210,16 @@ def __init__( ), "STAC_FASTAPI_VERSION": version, "ENABLED_EXTENSIONS": ",".join(stac_enabled_extensions), + "ENABLE_CATALOGS_EXTENSION": "true" if catalogs_enabled else "false", + "HIDE_ALTERNATE_PARENTS": ( + "true" if catalogs_config.hide_alternate_parents else "false" + ), **( { - "MAAP_TRANSACTION_AUTH_MODE": transactions_config.auth_mode, + "MAAP_TRANSACTION_AUTH_MODE": write_transactions_config.auth_mode, "MAAP_TRANSACTION_AUTH_SECRET_ARN": transaction_auth_secret.secret_arn, # type: ignore[union-attr] } - if transactions_config + if write_transactions_config else {} ), } @@ -258,8 +288,8 @@ def __init__( ), string_value=transaction_auth_secret.secret_arn, description=( - f"Secrets Manager ARN for MAAP {type} STAC collection " - f"transaction auth ({stage})" + f"Secrets Manager ARN for MAAP {type} STAC transaction auth " + f"({stage})" ), ) diff --git a/cdk/runtimes/eoapi/stac/tests/test_app.py b/cdk/runtimes/eoapi/stac/tests/test_app.py index d20dfb7..79c2e51 100644 --- a/cdk/runtimes/eoapi/stac/tests/test_app.py +++ b/cdk/runtimes/eoapi/stac/tests/test_app.py @@ -7,11 +7,8 @@ from eoapi.stac import auth from eoapi.stac.main import ( -<<<<<<< HEAD CATALOG_TRANSACTION_EXTENSION, CATALOGS_EXTENSION, -======= ->>>>>>> 8b10b607 (style: ruff format) COLLECTION_TRANSACTION_EXTENSION, create_app, parse_enabled_extensions, diff --git a/test/config.test.ts b/test/config.test.ts deleted file mode 100644 index a31ba24..0000000 --- a/test/config.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { Config } from "../cdk/config"; - -// Backup the original process.env -const originalEnv = process.env; - -describe("Config", () => { - beforeEach(() => { - // Reset process.env before each test - jest.resetModules(); - process.env = { ...originalEnv }; - - // Set required environment variables with default test values - process.env.STAGE = "test"; - process.env.DB_INSTANCE_TYPE = "t3.micro"; - process.env.JWKS_URL = "https://example.com/jwks"; - process.env.TITILER_DATA_ACCESS_ROLE_ARN = - "arn:aws:iam::123456789012:role/test-role"; - process.env.INGESTOR_DATA_ACCESS_ROLE_ARN = - "arn:aws:iam::123456789012:role/test-role"; - process.env.STAC_API_INTEGRATION_API_ARN = - "arn:aws:iam::123456789012:role/test-role"; - process.env.DB_ALLOCATED_STORAGE = "20"; - process.env.MOSAIC_HOST = "example.com"; - process.env.STAC_BROWSER_REPO_TAG = "latest"; - process.env.STAC_BROWSER_CUSTOM_DOMAIN_NAME = "stac-browser.example.com"; - process.env.STAC_BROWSER_CERTIFICATE_ARN = - "arn:aws:acm:us-east-1:123456789012:certificate/test-cert"; - process.env.STAC_API_CUSTOM_DOMAIN_NAME = "stac-api.example.com"; - process.env.PGSTAC_VERSION = "0.9.5"; - process.env.WEB_ACL_ARN = - "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl"; - - // Set optional values for testing - process.env.AUTHOR = "test-author"; - process.env.COMMIT_SHA = "abcdef123456"; - process.env.GIT_REPOSITORY = "test-repo"; - process.env.VERSION = "1.0.0"; - process.env.npm_package_version = "1.0.0"; - }); - - afterEach(() => { - // Restore original process.env after each test - process.env = originalEnv; - }); - - test("creates a valid configuration with required environment variables", () => { - const config = new Config(); - - // Test required string properties - expect(config.stage).toBe("test"); - expect(config.jwksUrl).toBe("https://example.com/jwks"); - expect(config.titilerDataAccessRoleArn).toBe( - "arn:aws:iam::123456789012:role/test-role", - ); - expect(config.ingestorDataAccessRoleArn).toBe( - "arn:aws:iam::123456789012:role/test-role", - ); - expect(config.stacApiIntegrationApiArn).toBe( - "arn:aws:iam::123456789012:role/test-role", - ); - expect(config.mosaicHost).toBe("example.com"); - expect(config.stacBrowserRepoTag).toBe("latest"); - expect(config.stacBrowserCustomDomainName).toBe("stac-browser.example.com"); - expect(config.stacBrowserCertificateArn).toBe( - "arn:aws:acm:us-east-1:123456789012:certificate/test-cert", - ); - expect(config.stacApiCustomDomainName).toBe("stac-api.example.com"); - expect(config.pgstacVersion).toBe("0.9.5"); - expect(config.webAclArn).toBe( - "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl", - ); - expect(config.userStacCollectionTransactions).toBeUndefined(); - expect(config.userStacCatalogs).toEqual({ enabled: true }); - - // Test number properties - expect(config.dbAllocatedStorage).toBe(20); - - // Test instance type - expect(config.dbInstanceType.toString()).toBe("t3.micro"); - - // Test tags - expect(config.tags.project).toBe("MAAP"); - expect(config.tags.version).toBe("1.0.0"); - expect(config.tags.stage).toBe("test"); - }); - - test("throws error when required environment variables are missing", () => { - // Remove a required environment variable - delete process.env.STAGE; - - // Should throw an error when creating a new Config - expect(() => new Config()).toThrow(/Must provide STAGE/); - }); - - test("handles optional environment variables correctly", () => { - process.env.CERTIFICATE_ARN = - "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert"; - process.env.INGESTOR_DOMAIN_NAME = "ingestor.example.com"; - process.env.TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME = "titiler.example.com"; - - const config = new Config(); - - expect(config.certificateArn).toBe( - "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert", - ); - expect(config.ingestorDomainName).toBe("ingestor.example.com"); - expect(config.titilerPgStacApiCustomDomainName).toBe("titiler.example.com"); - }); - - test("enables user STAC collection transactions with stack-managed auth by default", () => { - process.env.USER_STAC_COLLECTION_TRANSACTIONS_ENABLED = "true"; - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE = "basic"; - - const config = new Config(); - - expect(config.userStacCollectionTransactions).toEqual({ - authMode: "basic", - }); - }); - - test("accepts an explicit user STAC collection transaction secret ARN override", () => { - process.env.USER_STAC_COLLECTION_TRANSACTIONS_ENABLED = "true"; - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE = "basic"; - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN = - "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth"; - - const config = new Config(); - - expect(config.userStacCollectionTransactions).toEqual({ - authMode: "basic", - authSecretArn: - "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth", - }); - }); - - test("rejects unsupported user STAC collection transaction auth modes", () => { - process.env.USER_STAC_COLLECTION_TRANSACTIONS_ENABLED = "true"; - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE = "jwt"; - process.env.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN = - "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth"; - - expect(() => new Config()).toThrow(/Expected \"basic\"/); - }); - - test("rejects invalid user STAC collection transaction boolean values", () => { - process.env.USER_STAC_COLLECTION_TRANSACTIONS_ENABLED = "yes"; - - expect(() => new Config()).toThrow( - /USER_STAC_COLLECTION_TRANSACTIONS_ENABLED/, - ); - }); - - test("configures user STAC catalogs and catalog transactions", () => { - process.env.USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS = "true"; - process.env.USER_STAC_CATALOG_TRANSACTIONS_ENABLED = "true"; - process.env.USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE = "basic"; - process.env.USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN = - "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-catalog-auth"; - - const config = new Config(); - - expect(config.userStacCatalogs).toEqual({ - enabled: true, - hideAlternateParents: true, - transactions: { - authMode: "basic", - authSecretArn: - "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-catalog-auth", - }, - }); - }); - - test("rejects catalog transactions when user STAC catalogs are disabled", () => { - process.env.USER_STAC_CATALOGS_ENABLED = "false"; - process.env.USER_STAC_CATALOG_TRANSACTIONS_ENABLED = "true"; - process.env.USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE = "basic"; - - expect(() => new Config()).toThrow(/requires USER_STAC_CATALOGS_ENABLED/); - }); - - test("buildStackName formats properly", () => { - const config = new Config(); - - expect(config.buildStackName("TestService")).toBe( - "MAAP-STAC-test-TestService", - ); - }); - - // Remove the failing test for now - it seems the aws_ec2.InstanceType constructor - // doesn't throw an error with invalid types in the test environment - test.skip("throws error for invalid DB instance type", () => { - process.env.DB_INSTANCE_TYPE = "not-a-valid-instance-type"; - - let threwError = false; - try { - new Config(); - } catch (error) { - threwError = true; - expect((error as Error).message).toContain("Invalid DB_INSTANCE_TYPE"); - } - - expect(threwError).toBe(true); - }); -}); - diff --git a/test/pgstac-infra.test.ts b/test/pgstac-infra.test.ts deleted file mode 100644 index 62ee6e4..0000000 --- a/test/pgstac-infra.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as lambda from "aws-cdk-lib/aws-lambda"; -import { Match, Template } from "aws-cdk-lib/assertions"; -import { PgStacInfra, Props } from "../cdk/PgStacInfra"; - -function buildTemplate(overrides: Partial = {}): Template { - const app = new cdk.App(); - const networkStack = new cdk.Stack(app, "NetworkStack"); - const vpc = new ec2.Vpc(networkStack, "Vpc", { - maxAzs: 2, - natGateways: 1, - subnetConfiguration: [ - { - name: "public", - subnetType: ec2.SubnetType.PUBLIC, - }, - { - name: "private", - subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, - }, - { - name: "isolated", - subnetType: ec2.SubnetType.PRIVATE_ISOLATED, - }, - ], - }); - - const stack = new PgStacInfra(app, "TestPgStacInfra", { - vpc, - stage: "test", - type: "internal", - version: "1.0.0", - webAclArn: - "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl", - loggingBucketArn: "arn:aws:s3:::test-logging-bucket", - pgstacDbConfig: { - instanceType: new ec2.InstanceType("t3.micro"), - subnetPublic: false, - allocatedStorage: 20, - pgstacVersion: "0.9.5", - }, - stacApiConfig: { - customDomainName: "stac-api.example.com", - }, - titilerPgstacConfig: { - mosaicHost: "example.com/table-name", - bucketsPath: "./titiler_buckets.yaml", - dataAccessRoleArn: "arn:aws:iam::123456789012:role/test-role", - customDomainName: "titiler.example.com", - }, - ...overrides, - }); - - return Template.fromStack(stack); -} - -describe("PgStacInfra STAC runtime wiring", () => { - beforeAll(() => { - jest - .spyOn(lambda.Code, "fromDockerBuild") - .mockImplementation(() => lambda.Code.fromAsset("test")); - }); - - afterAll(() => { - jest.restoreAllMocks(); - }); - - test("uses the custom STAC handler and keeps transactions disabled by default", () => { - const template = buildTemplate({ - type: "public", - stacApiConfig: { - customDomainName: "public-stac.example.com", - integrationApiArn: - "arn:aws:execute-api:us-west-2:123456789012:api-id/stage/GET/", - }, - }); - - template.hasResourceProperties("AWS::Lambda::Function", { - Handler: "eoapi.stac.handler.handler", - Environment: { - Variables: Match.objectLike({ - STAC_FASTAPI_TITLE: "MAAP public STAC API (test)", - STAC_FASTAPI_LANDING_ID: "maap-public-stac-api-test", - ENABLED_EXTENSIONS: - "query,sort,fields,filter,free_text,pagination,collection_search,catalogs", - ENABLE_CATALOGS_EXTENSION: "true", - HIDE_ALTERNATE_PARENTS: "false", - }), - }, - }); - - expect( - Object.keys( - template.findResources("AWS::SecretsManager::Secret", { - Properties: { - Name: - "/maap-eoapi/test/public/stac-collection-transaction-basic-auth", - }, - }), - ), - ).toHaveLength(0); - template.resourceCountIs("AWS::SSM::Parameter", 1); - }); - - test("enables collection transactions with a stack-managed secret by default", () => { - const template = buildTemplate({ - stacApiConfig: { - customDomainName: "internal-stac.example.com", - transactions: { - authMode: "basic", - }, - }, - }); - - template.hasResourceProperties("AWS::SecretsManager::Secret", { - Description: - "Basic auth secret for MAAP internal STAC collection transactions (test)", - Name: "/maap-eoapi/test/internal/stac-collection-transaction-basic-auth", - GenerateSecretString: Match.objectLike({ - GenerateStringKey: "password", - SecretStringTemplate: '{"username":"maap-internal-stac-writer"}', - }), - }); - - template.hasResourceProperties("AWS::Lambda::Function", { - Handler: "eoapi.stac.handler.handler", - Environment: { - Variables: Match.objectLike({ - ENABLED_EXTENSIONS: - "query,sort,fields,filter,free_text,pagination,collection_search,catalogs,collection_transaction", - MAAP_TRANSACTION_AUTH_MODE: "basic", - MAAP_TRANSACTION_AUTH_SECRET_ARN: { - Ref: Match.stringLikeRegexp( - "staccollectiontransactionauthsecret", - ), - }, - }), - }, - }); - - template.hasResourceProperties("AWS::SSM::Parameter", { - Name: - "/maap-eoapi/test/internal/stac-collection-transaction-auth-secret-arn", - }); - }); - - test("enables catalog transactions with a stack-managed secret", () => { - const template = buildTemplate({ - stacApiConfig: { - customDomainName: "internal-stac.example.com", - catalogs: { - enabled: true, - hideAlternateParents: true, - transactions: { - authMode: "basic", - }, - }, - }, - }); - - template.hasResourceProperties("AWS::Lambda::Function", { - Handler: "eoapi.stac.handler.handler", - Environment: { - Variables: Match.objectLike({ - ENABLED_EXTENSIONS: - "query,sort,fields,filter,free_text,pagination,collection_search,catalogs,catalog_transaction", - ENABLE_CATALOGS_EXTENSION: "true", - HIDE_ALTERNATE_PARENTS: "true", - MAAP_TRANSACTION_AUTH_MODE: "basic", - MAAP_TRANSACTION_AUTH_SECRET_ARN: { - Ref: Match.stringLikeRegexp( - "staccollectiontransactionauthsecret", - ), - }, - }), - }, - }); - }); - - test("supports catalog transactions without collection transactions", () => { - const template = buildTemplate({ - stacApiConfig: { - customDomainName: "internal-stac.example.com", - catalogs: { - enabled: true, - transactions: { - authMode: "basic", - }, - }, - }, - }); - - template.hasResourceProperties("AWS::Lambda::Function", { - Handler: "eoapi.stac.handler.handler", - Environment: { - Variables: Match.objectLike({ - ENABLED_EXTENSIONS: - "query,sort,fields,filter,free_text,pagination,collection_search,catalogs,catalog_transaction", - MAAP_TRANSACTION_AUTH_MODE: "basic", - }), - }, - }); - template.hasResourceProperties("AWS::SSM::Parameter", { - Name: - "/maap-eoapi/test/internal/stac-collection-transaction-auth-secret-arn", - }); - }); - - test("rejects catalog transactions when catalogs are disabled", () => { - expect(() => - buildTemplate({ - stacApiConfig: { - customDomainName: "internal-stac.example.com", - catalogs: { - enabled: false, - transactions: { - authMode: "basic", - }, - }, - }, - }), - ).toThrow(/catalog transactions require catalogs/); - }); - - test("uses an explicit transaction auth secret ARN override when provided", () => { - const template = buildTemplate({ - stacApiConfig: { - customDomainName: "internal-stac.example.com", - transactions: { - authMode: "basic", - authSecretArn: - "arn:aws:secretsmanager:us-west-2:123456789012:secret:existing-auth-abcdef", - }, - }, - }); - - expect( - Object.keys( - template.findResources("AWS::SecretsManager::Secret", { - Properties: { - Name: - "/maap-eoapi/test/internal/stac-collection-transaction-basic-auth", - }, - }), - ), - ).toHaveLength(0); - template.hasResourceProperties("AWS::Lambda::Function", { - Handler: "eoapi.stac.handler.handler", - Environment: { - Variables: Match.objectLike({ - MAAP_TRANSACTION_AUTH_SECRET_ARN: - "arn:aws:secretsmanager:us-west-2:123456789012:secret:existing-auth-abcdef", - }), - }, - }); - }); -}); diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..2fa743b --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import pytest + +from cdk.config import Config + + +@pytest.fixture +def required_env(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("STAGE", "test") + monkeypatch.setenv("DB_INSTANCE_TYPE", "t3.micro") + monkeypatch.setenv("JWKS_URL", "https://example.com/jwks") + monkeypatch.setenv( + "TITILER_DATA_ACCESS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role" + ) + monkeypatch.setenv( + "INGESTOR_DATA_ACCESS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role" + ) + monkeypatch.setenv( + "STAC_API_INTEGRATION_API_ARN", "arn:aws:iam::123456789012:role/test-role" + ) + monkeypatch.setenv("DB_ALLOCATED_STORAGE", "20") + monkeypatch.setenv("MOSAIC_HOST", "example.com") + monkeypatch.setenv("STAC_BROWSER_REPO_TAG", "latest") + monkeypatch.setenv("STAC_BROWSER_CUSTOM_DOMAIN_NAME", "stac-browser.example.com") + monkeypatch.setenv( + "STAC_BROWSER_CERTIFICATE_ARN", + "arn:aws:acm:us-east-1:123456789012:certificate/test-cert", + ) + monkeypatch.setenv("STAC_API_CUSTOM_DOMAIN_NAME", "stac-api.example.com") + monkeypatch.setenv("PGSTAC_VERSION", "0.9.5") + monkeypatch.setenv( + "WEB_ACL_ARN", "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl" + ) + monkeypatch.setenv("VERSION", "1.0.0") + + +def test_creates_valid_config_with_required_env(required_env: None) -> None: + config = Config() + + assert config.stage == "test" + assert config.jwks_url == "https://example.com/jwks" + assert config.titiler_data_access_role_arn == "arn:aws:iam::123456789012:role/test-role" + assert config.ingestor_data_access_role_arn == "arn:aws:iam::123456789012:role/test-role" + assert config.stac_api_integration_api_arn == "arn:aws:iam::123456789012:role/test-role" + assert config.mosaic_host == "example.com" + assert config.stac_browser_repo_tag == "latest" + assert config.stac_browser_custom_domain_name == "stac-browser.example.com" + assert config.stac_browser_certificate_arn == ( + "arn:aws:acm:us-east-1:123456789012:certificate/test-cert" + ) + assert config.stac_api_custom_domain_name == "stac-api.example.com" + assert config.pgstac_version == "0.9.5" + assert config.web_acl_arn == "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl" + assert config.user_stac_collection_transactions is None + assert config.user_stac_catalogs is not None + assert config.user_stac_catalogs.enabled is True + + assert config.db_allocated_storage == 20 + assert config.db_instance_type.to_string() == "t3.micro" + + assert config.tags["project"] == "MAAP" + assert config.tags["version"] == "1.0.0" + assert config.tags["stage"] == "test" + + +def test_missing_required_env_raises(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("STAGE", raising=False) + with pytest.raises(Exception): + Config() + + +def test_optional_env_vars(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv( + "CERTIFICATE_ARN", "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert" + ) + monkeypatch.setenv("INGESTOR_DOMAIN_NAME", "ingestor.example.com") + monkeypatch.setenv("TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME", "titiler.example.com") + + config = Config() + + assert config.certificate_arn == ( + "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert" + ) + assert config.ingestor_domain_name == "ingestor.example.com" + assert config.titiler_pg_stac_api_custom_domain_name == "titiler.example.com" + + +def test_user_stac_collection_transactions_defaults(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "basic") + + config = Config() + assert config.user_stac_collection_transactions is not None + assert config.user_stac_collection_transactions.auth_mode == "basic" + assert config.user_stac_collection_transactions.auth_secret_arn is None + + +def test_user_stac_collection_transactions_accepts_secret(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "basic") + monkeypatch.setenv( + "USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN", + "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth", + ) + + config = Config() + assert config.user_stac_collection_transactions is not None + assert config.user_stac_collection_transactions.auth_mode == "basic" + assert config.user_stac_collection_transactions.auth_secret_arn == ( + "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth" + ) + + +def test_user_stac_collection_transactions_rejects_unsupported_auth_mode( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "jwt") + + with pytest.raises(ValueError, match='Expected "basic"'): + Config() + + +def test_user_stac_collection_transactions_rejects_invalid_boolean( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "yes") + + with pytest.raises(ValueError, match="Expected 'true' or 'false'"): + Config() + + +def test_user_stac_catalogs_and_catalog_transactions(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS", "true") + monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_ENABLED", "true") + monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE", "basic") + monkeypatch.setenv( + "USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN", + "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-catalog-auth", + ) + + config = Config() + assert config.user_stac_catalogs is not None + assert config.user_stac_catalogs.enabled is True + assert config.user_stac_catalogs.hide_alternate_parents is True + assert config.user_stac_catalogs.transactions is not None + assert config.user_stac_catalogs.transactions.auth_mode == "basic" + assert config.user_stac_catalogs.transactions.auth_secret_arn == ( + "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-catalog-auth" + ) + + +def test_rejects_catalog_transactions_when_catalogs_disabled( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("USER_STAC_CATALOGS_ENABLED", "false") + monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_ENABLED", "true") + monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE", "basic") + + with pytest.raises(ValueError, match="requires USER_STAC_CATALOGS_ENABLED"): + Config() + + +def test_build_stack_name(required_env: None) -> None: + config = Config() + assert config.build_stack_name("TestService") == "MAAP-STAC-test-TestService" diff --git a/tests/test_pgstac_infra.py b/tests/test_pgstac_infra.py index d8c7d3f..0b71c5d 100644 --- a/tests/test_pgstac_infra.py +++ b/tests/test_pgstac_infra.py @@ -1,19 +1,21 @@ -"""Tests for PgStacInfra stack - Python equivalent of test/pgstac-infra.test.ts""" +"""Tests for PgStacInfra stack wiring and transaction/catalog behavior.""" from __future__ import annotations from unittest.mock import patch import aws_cdk as cdk +import pytest from aws_cdk import assertions, aws_ec2 as ec2 -from cdk.pgstac_infra import ( +from cdk.config import ( CollectionTransactionsConfig, PgStacDbConfig, - PgStacInfra, StacApiConfig, + StacCatalogsConfig, TitilerPgstacConfig, ) +from cdk.pgstac_infra import PgStacInfra # Minimal required props for test builds BASE_TITILER_CONFIG = TitilerPgstacConfig( @@ -64,7 +66,7 @@ def build_template(overrides: dict | None = None) -> assertions.Template: props |= overrides or {} # Mock lambda Code.from_docker_build so tests don't need Docker - mock_code = cdk.aws_lambda.Code.from_asset("test") + mock_code = cdk.aws_lambda.Code.from_asset("tests") with patch("aws_cdk.aws_lambda.Code.from_docker_build", return_value=mock_code): stack = PgStacInfra(app, "TestPgStacInfra", **props) @@ -96,8 +98,10 @@ def test_uses_custom_stac_handler_and_keeps_transactions_disabled_by_default(sel "STAC_FASTAPI_LANDING_ID": "maap-public-stac-api-test", "ENABLED_EXTENSIONS": ( "query,sort,fields,filter,free_text," - "pagination,collection_search" + "pagination,collection_search,catalogs" ), + "ENABLE_CATALOGS_EXTENSION": "true", + "HIDE_ALTERNATE_PARENTS": "false", } ) }, @@ -138,7 +142,9 @@ def test_enables_collection_transactions_with_stack_managed_secret(self): "GenerateSecretString": assertions.Match.object_like( { "GenerateStringKey": "password", - "SecretStringTemplate": '{"username":"maap-internal-stac-writer"}', + "SecretStringTemplate": assertions.Match.string_like_regexp( + r'\{"username"\s*:\s*"maap-internal-stac-writer"\}' + ), } ), }, @@ -153,7 +159,7 @@ def test_enables_collection_transactions_with_stack_managed_secret(self): { "ENABLED_EXTENSIONS": ( "query,sort,fields,filter,free_text,pagination," - "collection_search,collection_transaction" + "collection_search,catalogs,collection_transaction" ), "MAAP_TRANSACTION_AUTH_MODE": "basic", } @@ -171,6 +177,93 @@ def test_enables_collection_transactions_with_stack_managed_secret(self): }, ) + def test_enables_catalog_transactions_with_stack_managed_secret(self): + template = build_template( + { + "stac_api_config": StacApiConfig( + custom_domain_name="internal-stac.example.com", + catalogs=StacCatalogsConfig( + enabled=True, + hide_alternate_parents=True, + transactions=CollectionTransactionsConfig(auth_mode="basic"), + ), + ), + } + ) + + template.has_resource_properties( + "AWS::Lambda::Function", + { + "Handler": "eoapi.stac.handler.handler", + "Environment": { + "Variables": assertions.Match.object_like( + { + "ENABLED_EXTENSIONS": ( + "query,sort,fields,filter,free_text,pagination," + "collection_search,catalogs,catalog_transaction" + ), + "ENABLE_CATALOGS_EXTENSION": "true", + "HIDE_ALTERNATE_PARENTS": "true", + "MAAP_TRANSACTION_AUTH_MODE": "basic", + } + ) + }, + }, + ) + + def test_supports_catalog_transactions_without_collection_transactions(self): + template = build_template( + { + "stac_api_config": StacApiConfig( + custom_domain_name="internal-stac.example.com", + catalogs=StacCatalogsConfig( + enabled=True, + transactions=CollectionTransactionsConfig(auth_mode="basic"), + ), + ), + } + ) + + template.has_resource_properties( + "AWS::Lambda::Function", + { + "Handler": "eoapi.stac.handler.handler", + "Environment": { + "Variables": assertions.Match.object_like( + { + "ENABLED_EXTENSIONS": ( + "query,sort,fields,filter,free_text,pagination," + "collection_search,catalogs,catalog_transaction" + ), + "MAAP_TRANSACTION_AUTH_MODE": "basic", + } + ) + }, + }, + ) + template.has_resource_properties( + "AWS::SSM::Parameter", + { + "Name": ( + "/maap-eoapi/test/internal/stac-collection-transaction-auth-secret-arn" + ) + }, + ) + + def test_rejects_catalog_transactions_when_catalogs_disabled(self): + with pytest.raises(ValueError, match="catalog transactions require catalogs"): + build_template( + { + "stac_api_config": StacApiConfig( + custom_domain_name="internal-stac.example.com", + catalogs=StacCatalogsConfig( + enabled=False, + transactions=CollectionTransactionsConfig(auth_mode="basic"), + ), + ), + } + ) + def test_uses_explicit_transaction_auth_secret_arn_override(self): template = build_template( { From 3ab7ac658d49656619c0cfb4a79290d47b2bb85f Mon Sep 17 00:00:00 2001 From: Jamison French Date: Thu, 2 Jul 2026 15:04:51 -0500 Subject: [PATCH 8/8] chore: PR suggestions, ruff format --- .github/workflows/deploy.yml | 2 - README.md | 6 +- app.py | 14 +- cdk/config.py | 161 ++++++++++++--------- cdk/pgstac_infra.py | 5 +- cdk/runtimes/eoapi/stac/tests/test_app.py | 16 +- cdk/runtimes/eoapi/stac/tests/test_auth.py | 4 +- tests/test_config.py | 51 ++++--- tests/test_pgstac_infra.py | 4 +- 9 files changed, 152 insertions(+), 111 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5c29e1c..af60d21 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,12 +48,10 @@ jobs: USER_STAC_COLLECTION_ID_REGISTRY: ${{ vars.USER_STAC_COLLECTION_ID_REGISTRY }} USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE: ${{ vars.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE }} USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN: ${{ vars.USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN }} - USER_STAC_COLLECTION_TRANSACTIONS_ENABLED: ${{ vars.USER_STAC_COLLECTION_TRANSACTIONS_ENABLED }} USER_STAC_CATALOGS_ENABLED: ${{ vars.USER_STAC_CATALOGS_ENABLED }} USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS: ${{ vars.USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS }} USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE: ${{ vars.USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE }} USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN: ${{ vars.USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN }} - USER_STAC_CATALOG_TRANSACTIONS_ENABLED: ${{ vars.USER_STAC_CATALOG_TRANSACTIONS_ENABLED }} USER_STAC_STAC_API_CUSTOM_DOMAIN_NAME: ${{ vars.USER_STAC_STAC_API_CUSTOM_DOMAIN_NAME }} USER_STAC_TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME: ${{ vars.USER_STAC_TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME }} WEB_ACL_ARN: ${{ vars.WEB_ACL_ARN }} diff --git a/README.md b/README.md index 52d4ced..96c307c 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,11 @@ User STAC catalog configuration: - `USER_STAC_CATALOGS_ENABLED=false` disables read-only `/catalogs` routes. - `USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS=true` hides alternate parent links in catalog responses. -- `USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true` enables catalog write routes. This requires catalogs to stay enabled. -- `USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE=basic` selects the supported auth mode. +- `USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE=basic` enables catalog write routes and selects the supported auth mode. Catalog write routes require catalogs to stay enabled. - `USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN` can point at an existing auth secret. Collection-only STAC transactions can still be enabled with: -- `USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true` - `USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE=basic` When either collection or catalog transactions are enabled, this CDK stack creates and manages the Secrets Manager secret used for STAC basic auth by default, grants the STAC Lambda read access to it, and publishes the secret ARN to SSM at: @@ -69,7 +67,7 @@ For a catalogs-enabled deployment, verify: - OpenAPI includes read-only catalog routes such as `GET /catalogs`, `GET /catalogs/{catalog_id}`, and catalog-scoped collection/item reads. - `GET /` includes `rel="child"` links for listed catalogs so STAC Browser can discover catalog roots. -- catalog write routes are absent unless `USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true`. +- catalog write routes are absent unless `USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE=basic` is configured. For a transaction-enabled internal deployment, verify: diff --git a/app.py b/app.py index fd0b8ba..fcc7d47 100644 --- a/app.py +++ b/app.py @@ -41,8 +41,8 @@ web_acl_arn=config.web_acl_arn, logging_bucket_arn=common.logging_bucket.bucket_arn, pgstac_db_config=config.pgstac_db(), - stac_api_config=config.stac_api(), - titiler_pgstac_config=config.titiler_pgstac(), + stac_api_config=config.public_stac_api(), + titiler_pgstac_config=config.public_titiler_pgstac(), stac_browser_config=config.stac_browser(), ingestor_config=config.ingestor(), add_stactools_item_generator=True, @@ -61,10 +61,14 @@ web_acl_arn=config.web_acl_arn, logging_bucket_arn=common.logging_bucket.bucket_arn, pgstac_db_config=config.pgstac_db(), - stac_api_config=config.stac_api(user_stac=True), - titiler_pgstac_config=config.titiler_pgstac(user_stac=True), + stac_api_config=config.user_stac_api(), + titiler_pgstac_config=config.user_titiler_pgstac(), add_stactools_item_generator=False, - **({"dps_stac_item_gen_config": dps_stac_item_gen_config} if dps_stac_item_gen_config else {}), + **( + {"dps_stac_item_gen_config": dps_stac_item_gen_config} + if dps_stac_item_gen_config + else {} + ), termination_protection=False, ) diff --git a/cdk/config.py b/cdk/config.py index 12824ad..464cdc2 100644 --- a/cdk/config.py +++ b/cdk/config.py @@ -113,17 +113,15 @@ class Config(BaseSettings): user_stac_inbound_topic_arns: Optional[list[str]] = None user_stac_collection_id_registry: Optional[dict[str, list[str]]] = None - # --- Collection transactions sub-fields (assembled by model_validator below) --- - user_stac_collection_transactions_enabled: Optional[bool] = None + # --- Collection transactions env fields --- user_stac_collection_transactions_auth_mode: Optional[str] = None user_stac_collection_transactions_auth_secret_arn: Optional[str] = None - user_stac_collection_transactions: Optional[CollectionTransactionsConfig] = None - user_stac_catalogs_enabled: Optional[bool] = None + + # --- Catalog env fields --- + user_stac_catalogs_enabled: bool = True user_stac_catalogs_hide_alternate_parents: Optional[bool] = None - user_stac_catalog_transactions_enabled: Optional[bool] = None user_stac_catalog_transactions_auth_mode: Optional[str] = None user_stac_catalog_transactions_auth_secret_arn: Optional[str] = None - user_stac_catalogs: Optional[StacCatalogsConfig] = None @field_validator("db_instance_type", mode="before") @classmethod @@ -136,10 +134,7 @@ def parse_instance_type(cls, v: object) -> ec2.InstanceType: raise ValueError(f"Invalid DB_INSTANCE_TYPE: {v!r}") from e @field_validator( - "user_stac_collection_transactions_enabled", - "user_stac_catalogs_enabled", "user_stac_catalogs_hide_alternate_parents", - "user_stac_catalog_transactions_enabled", mode="before", ) @classmethod @@ -157,68 +152,94 @@ def parse_optional_bool_env(cls, v: object) -> Optional[bool]: if normalized == "false": return False - raise ValueError( - f"Invalid boolean value: {v!r}. Expected 'true' or 'false'." - ) + raise ValueError(f"Invalid boolean value: {v!r}. Expected 'true' or 'false'.") @model_validator(mode="after") - def assemble_collection_transactions(self) -> Config: - if self.user_stac_collection_transactions_enabled is not True: - self.user_stac_collection_transactions = None - else: - if not self.user_stac_collection_transactions_auth_mode: - raise ValueError( - "Must provide USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE " - "when USER_STAC_COLLECTION_TRANSACTIONS_ENABLED=true" - ) - if self.user_stac_collection_transactions_auth_mode != "basic": + def validate_required_pairs(self) -> Config: + """Validate that related configuration fields are present together.""" + pairs = [ + ( + "user_stac_collection_transactions_auth_secret_arn", + "user_stac_collection_transactions_auth_mode", + ), + ( + "user_stac_catalog_transactions_auth_secret_arn", + "user_stac_catalog_transactions_auth_mode", + ), + ( + "user_stac_catalog_transactions_auth_mode", + "user_stac_catalogs_enabled", + ), + ] + for trigger, required in pairs: + if getattr(self, trigger) and not getattr(self, required): raise ValueError( - "Unsupported USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE: " - f"{self.user_stac_collection_transactions_auth_mode}. " - 'Expected "basic".' + f"{required.upper()} is required when {trigger.upper()} is set" ) - self.user_stac_collection_transactions = CollectionTransactionsConfig( - auth_mode=self.user_stac_collection_transactions_auth_mode, - auth_secret_arn=self.user_stac_collection_transactions_auth_secret_arn, + return self + + @model_validator(mode="after") + def validate_catalogs_config(self) -> Config: + enabled = self.user_stac_catalogs_enabled + auth_mode = self.user_stac_catalog_transactions_auth_mode + auth_secret_arn = self.user_stac_catalog_transactions_auth_secret_arn + + if (auth_mode or auth_secret_arn) and not enabled: + raise ValueError( + "USER_STAC_CATALOGS_ENABLED must be True when " + "USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE or " + "USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN is set." ) - catalogs_enabled = self.user_stac_catalogs_enabled - if catalogs_enabled is None: - catalogs_enabled = True + if (auth_mode or auth_secret_arn) and auth_mode != "basic": + raise ValueError( + f"Unsupported USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE: " + f'"{auth_mode}". Expected "basic".' + ) + + return self + + @model_validator(mode="after") + def validate_collection_transactions(self) -> Config: + auth_mode = self.user_stac_collection_transactions_auth_mode + auth_secret_arn = self.user_stac_collection_transactions_auth_secret_arn - catalog_transactions_enabled = self.user_stac_catalog_transactions_enabled is True - if catalog_transactions_enabled and not catalogs_enabled: + if (auth_mode or auth_secret_arn) and auth_mode != "basic": raise ValueError( - "USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true requires " - "USER_STAC_CATALOGS_ENABLED=true" + f"Unsupported USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE: " + f'"{auth_mode}". Expected "basic".' ) - catalogs_transactions: Optional[CollectionTransactionsConfig] = None - if catalog_transactions_enabled: - if not self.user_stac_catalog_transactions_auth_mode: - raise ValueError( - "Must provide USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE when " - "USER_STAC_CATALOG_TRANSACTIONS_ENABLED=true" - ) - if self.user_stac_catalog_transactions_auth_mode != "basic": - raise ValueError( - "Unsupported USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE: " - f"{self.user_stac_catalog_transactions_auth_mode}. " - 'Expected "basic".' - ) - catalogs_transactions = CollectionTransactionsConfig( + return self + + @computed_field # type: ignore[prop-decorator] + @property + def user_stac_collection_transactions( + self, + ) -> Optional[CollectionTransactionsConfig]: + if self.user_stac_collection_transactions_auth_mode is None: + return None + return CollectionTransactionsConfig( + auth_mode=self.user_stac_collection_transactions_auth_mode, + auth_secret_arn=self.user_stac_collection_transactions_auth_secret_arn, + ) + + @computed_field # type: ignore[prop-decorator] + @property + def user_stac_catalogs(self) -> StacCatalogsConfig: + catalog_transactions: Optional[CollectionTransactionsConfig] = None + if self.user_stac_catalog_transactions_auth_mode is not None: + catalog_transactions = CollectionTransactionsConfig( auth_mode=self.user_stac_catalog_transactions_auth_mode, auth_secret_arn=self.user_stac_catalog_transactions_auth_secret_arn, ) - self.user_stac_catalogs = StacCatalogsConfig( - enabled=catalogs_enabled, + return StacCatalogsConfig( + enabled=self.user_stac_catalogs_enabled, hide_alternate_parents=self.user_stac_catalogs_hide_alternate_parents, - transactions=catalogs_transactions, + transactions=catalog_transactions, ) - return self - @computed_field # type: ignore[prop-decorator] @property def tags(self) -> dict[str, str]: @@ -235,29 +256,33 @@ def pgstac_db(self) -> PgStacDbConfig: subnet_public=False, ) - def stac_api(self, *, user_stac: bool = False) -> StacApiConfig: - if user_stac: - return StacApiConfig( - custom_domain_name=self.user_stac_stac_api_custom_domain_name, - transactions=self.user_stac_collection_transactions, - catalogs=self.user_stac_catalogs, - ) - + def public_stac_api(self) -> StacApiConfig: return StacApiConfig( custom_domain_name=self.stac_api_custom_domain_name, integration_api_arn=self.stac_api_integration_api_arn, catalogs=StacCatalogsConfig(enabled=True), ) - def titiler_pgstac(self, *, user_stac: bool = False) -> TitilerPgstacConfig: + def user_stac_api(self) -> StacApiConfig: + return StacApiConfig( + custom_domain_name=self.user_stac_stac_api_custom_domain_name, + transactions=self.user_stac_collection_transactions, + catalogs=self.user_stac_catalogs, + ) + + def public_titiler_pgstac(self) -> TitilerPgstacConfig: return TitilerPgstacConfig( mosaic_host=self.mosaic_host, buckets_path="./titiler_buckets.yaml", - custom_domain_name=( - self.user_stac_titiler_pgstac_api_custom_domain_name - if user_stac - else self.titiler_pg_stac_api_custom_domain_name - ), + custom_domain_name=self.titiler_pg_stac_api_custom_domain_name, + data_access_role_arn=self.titiler_data_access_role_arn, + ) + + def user_titiler_pgstac(self) -> TitilerPgstacConfig: + return TitilerPgstacConfig( + mosaic_host=self.mosaic_host, + buckets_path="./titiler_buckets.yaml", + custom_domain_name=self.user_stac_titiler_pgstac_api_custom_domain_name, data_access_role_arn=self.titiler_data_access_role_arn, ) diff --git a/cdk/pgstac_infra.py b/cdk/pgstac_infra.py index 23e4f77..408c150 100644 --- a/cdk/pgstac_infra.py +++ b/cdk/pgstac_infra.py @@ -139,7 +139,10 @@ def __init__( f"Unsupported STAC collection transaction auth mode: " f"{transactions_config.auth_mode}" ) - if catalog_transactions_config and catalog_transactions_config.auth_mode != "basic": + if ( + catalog_transactions_config + and catalog_transactions_config.auth_mode != "basic" + ): raise ValueError( f"Unsupported STAC catalog transaction auth mode: " f"{catalog_transactions_config.auth_mode}" diff --git a/cdk/runtimes/eoapi/stac/tests/test_app.py b/cdk/runtimes/eoapi/stac/tests/test_app.py index 79c2e51..a0681da 100644 --- a/cdk/runtimes/eoapi/stac/tests/test_app.py +++ b/cdk/runtimes/eoapi/stac/tests/test_app.py @@ -74,7 +74,9 @@ def test_catalog_routes_are_enabled_by_default() -> None: assert set(openapi["paths"]["/catalogs"].keys()) == {"get"} assert "/catalogs/{catalog_id}" in openapi["paths"] assert set(openapi["paths"]["/catalogs/{catalog_id}"].keys()) == {"get"} - assert "/catalogs/{catalog_id}/collections/{collection_id}/items" in openapi["paths"] + assert ( + "/catalogs/{catalog_id}/collections/{collection_id}/items" in openapi["paths"] + ) assert "post" not in openapi["paths"]["/catalogs"] @@ -139,9 +141,7 @@ def test_catalog_transactions_are_opt_in() -> None: assert set(openapi["paths"]["/catalogs"].keys()) == {"get"} assert set(openapi["paths"]["/catalogs/{catalog_id}"].keys()) == {"get"} - assert set(openapi["paths"]["/catalogs/{catalog_id}/collections"].keys()) == { - "get" - } + assert set(openapi["paths"]["/catalogs/{catalog_id}/collections"].keys()) == {"get"} def test_catalog_transaction_routes_require_catalogs() -> None: @@ -188,13 +188,9 @@ def test_catalog_transaction_app_registers_catalog_write_routes( assert set( openapi["paths"]["/catalogs/{catalog_id}/catalogs/{sub_catalog_id}"].keys() ) == {"delete"} - assert openapi["paths"]["/catalogs"]["post"]["security"] == [ - {"HTTPBasic": []} - ] + assert openapi["paths"]["/catalogs"]["post"]["security"] == [{"HTTPBasic": []}] assert "security" not in openapi["paths"]["/catalogs"]["get"] - assert any( - "transaction" in item for item in app.state.catalogs_conformance_classes - ) + assert any("transaction" in item for item in app.state.catalogs_conformance_classes) def test_catalog_conformance_is_read_only_without_catalog_transactions() -> None: diff --git a/cdk/runtimes/eoapi/stac/tests/test_auth.py b/cdk/runtimes/eoapi/stac/tests/test_auth.py index 562cf4b..9271b37 100644 --- a/cdk/runtimes/eoapi/stac/tests/test_auth.py +++ b/cdk/runtimes/eoapi/stac/tests/test_auth.py @@ -222,9 +222,7 @@ def test_catalog_write_openapi_security_matches_collection_writes( """Catalog write operations should advertise HTTP Basic auth in OpenAPI.""" openapi = catalog_transaction_app.app.openapi() - assert openapi["paths"]["/catalogs"]["post"]["security"] == [ - {"HTTPBasic": []} - ] + assert openapi["paths"]["/catalogs"]["post"]["security"] == [{"HTTPBasic": []}] assert openapi["paths"]["/catalogs/{catalog_id}"]["put"]["security"] == [ {"HTTPBasic": []} ] diff --git a/tests/test_config.py b/tests/test_config.py index 2fa743b..f318a99 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -40,9 +40,18 @@ def test_creates_valid_config_with_required_env(required_env: None) -> None: assert config.stage == "test" assert config.jwks_url == "https://example.com/jwks" - assert config.titiler_data_access_role_arn == "arn:aws:iam::123456789012:role/test-role" - assert config.ingestor_data_access_role_arn == "arn:aws:iam::123456789012:role/test-role" - assert config.stac_api_integration_api_arn == "arn:aws:iam::123456789012:role/test-role" + assert ( + config.titiler_data_access_role_arn + == "arn:aws:iam::123456789012:role/test-role" + ) + assert ( + config.ingestor_data_access_role_arn + == "arn:aws:iam::123456789012:role/test-role" + ) + assert ( + config.stac_api_integration_api_arn + == "arn:aws:iam::123456789012:role/test-role" + ) assert config.mosaic_host == "example.com" assert config.stac_browser_repo_tag == "latest" assert config.stac_browser_custom_domain_name == "stac-browser.example.com" @@ -51,7 +60,10 @@ def test_creates_valid_config_with_required_env(required_env: None) -> None: ) assert config.stac_api_custom_domain_name == "stac-api.example.com" assert config.pgstac_version == "0.9.5" - assert config.web_acl_arn == "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl" + assert ( + config.web_acl_arn + == "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test-acl" + ) assert config.user_stac_collection_transactions is None assert config.user_stac_catalogs is not None assert config.user_stac_catalogs.enabled is True @@ -72,7 +84,8 @@ def test_missing_required_env_raises(monkeypatch: pytest.MonkeyPatch) -> None: def test_optional_env_vars(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv( - "CERTIFICATE_ARN", "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert" + "CERTIFICATE_ARN", + "arn:aws:acm:us-east-1:123456789012:certificate/optional-cert", ) monkeypatch.setenv("INGESTOR_DOMAIN_NAME", "ingestor.example.com") monkeypatch.setenv("TITILER_PGSTAC_API_CUSTOM_DOMAIN_NAME", "titiler.example.com") @@ -86,8 +99,9 @@ def test_optional_env_vars(required_env: None, monkeypatch: pytest.MonkeyPatch) assert config.titiler_pg_stac_api_custom_domain_name == "titiler.example.com" -def test_user_stac_collection_transactions_defaults(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") +def test_user_stac_collection_transactions_defaults( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "basic") config = Config() @@ -96,8 +110,9 @@ def test_user_stac_collection_transactions_defaults(required_env: None, monkeypa assert config.user_stac_collection_transactions.auth_secret_arn is None -def test_user_stac_collection_transactions_accepts_secret(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") +def test_user_stac_collection_transactions_accepts_secret( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "basic") monkeypatch.setenv( "USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN", @@ -115,25 +130,28 @@ def test_user_stac_collection_transactions_accepts_secret(required_env: None, mo def test_user_stac_collection_transactions_rejects_unsupported_auth_mode( required_env: None, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "true") monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_AUTH_MODE", "jwt") with pytest.raises(ValueError, match='Expected "basic"'): Config() -def test_user_stac_collection_transactions_rejects_invalid_boolean( +def test_user_stac_collection_transactions_requires_auth_mode_when_secret_set( required_env: None, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setenv("USER_STAC_COLLECTION_TRANSACTIONS_ENABLED", "yes") + monkeypatch.setenv( + "USER_STAC_COLLECTION_TRANSACTIONS_AUTH_SECRET_ARN", + "arn:aws:secretsmanager:us-west-2:123456789012:secret:user-stac-auth", + ) - with pytest.raises(ValueError, match="Expected 'true' or 'false'"): + with pytest.raises(ValueError, match="AUTH_MODE"): Config() -def test_user_stac_catalogs_and_catalog_transactions(required_env: None, monkeypatch: pytest.MonkeyPatch) -> None: +def test_user_stac_catalogs_and_catalog_transactions( + required_env: None, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.setenv("USER_STAC_CATALOGS_HIDE_ALTERNATE_PARENTS", "true") - monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_ENABLED", "true") monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE", "basic") monkeypatch.setenv( "USER_STAC_CATALOG_TRANSACTIONS_AUTH_SECRET_ARN", @@ -155,10 +173,9 @@ def test_rejects_catalog_transactions_when_catalogs_disabled( required_env: None, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setenv("USER_STAC_CATALOGS_ENABLED", "false") - monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_ENABLED", "true") monkeypatch.setenv("USER_STAC_CATALOG_TRANSACTIONS_AUTH_MODE", "basic") - with pytest.raises(ValueError, match="requires USER_STAC_CATALOGS_ENABLED"): + with pytest.raises(ValueError, match="USER_STAC_CATALOGS_ENABLED is required"): Config() diff --git a/tests/test_pgstac_infra.py b/tests/test_pgstac_infra.py index 0b71c5d..f91cf14 100644 --- a/tests/test_pgstac_infra.py +++ b/tests/test_pgstac_infra.py @@ -258,7 +258,9 @@ def test_rejects_catalog_transactions_when_catalogs_disabled(self): custom_domain_name="internal-stac.example.com", catalogs=StacCatalogsConfig( enabled=False, - transactions=CollectionTransactionsConfig(auth_mode="basic"), + transactions=CollectionTransactionsConfig( + auth_mode="basic" + ), ), ), }