From 120cd9b7fef739ff0bc99e556287f5cde0613e51 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 16:57:00 +0000 Subject: [PATCH 01/31] fix wonky rebase --- .github/workflows/ab-testing-checks.yml | 58 - .github/workflows/ab-testing-ci-code.yml | 26 - .github/workflows/ab-testing-ci.yml | 165 +- .github/workflows/ab-testing-deploy.yml | 42 - .github/workflows/ab-testing-ui.yml | 73 - ab-testing/.gitignore | 1 + ab-testing/cdk.json | 7 + ab-testing/cdk/bin/cdk.ts | 66 + .../dictionaryDeployLambda.test.ts.snap | 597 ++++++ .../cdk/lib/dictionaryDeployLambda.test.ts | 51 + ab-testing/cdk/lib/dictionaryDeployLambda.ts | 58 + .../dictionary-deploy-lambda/package.json | 39 + .../dictionary-deploy-lambda/rollup.config.js | 45 + .../src/deploy-dictionary.ts | 65 + .../dictionary-deploy-lambda/src/deploy.ts | 52 + .../src/fetch-artifact.ts | 42 + .../dictionary-deploy-lambda/src/index.ts | 61 + .../dictionary-deploy-lambda/tsconfig.json | 8 + ab-testing/{scripts => }/lib/config.ts | 0 ab-testing/{scripts => }/lib/constants.ts | 0 .../{scripts => }/lib/fastly-api.test.ts | 0 ab-testing/{scripts => }/lib/fastly-api.ts | 8 +- .../{scripts => }/lib/fastly-subfield.test.ts | 0 .../{scripts => }/lib/fastly-subfield.ts | 0 ab-testing/{scripts => }/lib/types.ts | 0 ab-testing/package.json | 11 +- ab-testing/riff-raff.yaml | 13 - .../scripts/build/build-ab-tests-dict.ts | 2 +- .../build/calculate-mvt-updates.test.ts | 4 +- .../scripts/build/calculate-mvt-updates.ts | 6 +- ab-testing/scripts/build/index.ts | 4 +- .../build/test-group-mvt-manager.test.ts | 2 +- .../scripts/build/test-group-mvt-manager.ts | 4 +- ab-testing/scripts/deploy/deploy-ab-tests.ts | 50 - ab-testing/scripts/deploy/deploy-mvts.ts | 51 - ab-testing/scripts/deploy/index.ts | 69 - .../scripts/deploy/read-built-dictionaries.ts | 32 - .../validation/limitServerSide.test.ts | 2 +- .../scripts/validation/limitServerSide.ts | 2 +- pnpm-lock.yaml | 1838 +++++++++-------- pnpm-workspace.yaml | 1 + 41 files changed, 2246 insertions(+), 1309 deletions(-) delete mode 100644 .github/workflows/ab-testing-checks.yml delete mode 100644 .github/workflows/ab-testing-ci-code.yml delete mode 100644 .github/workflows/ab-testing-deploy.yml delete mode 100644 .github/workflows/ab-testing-ui.yml create mode 100644 ab-testing/.gitignore create mode 100644 ab-testing/cdk.json create mode 100644 ab-testing/cdk/bin/cdk.ts create mode 100644 ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap create mode 100644 ab-testing/cdk/lib/dictionaryDeployLambda.test.ts create mode 100644 ab-testing/cdk/lib/dictionaryDeployLambda.ts create mode 100644 ab-testing/dictionary-deploy-lambda/package.json create mode 100644 ab-testing/dictionary-deploy-lambda/rollup.config.js create mode 100644 ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts create mode 100644 ab-testing/dictionary-deploy-lambda/src/deploy.ts create mode 100644 ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts create mode 100644 ab-testing/dictionary-deploy-lambda/src/index.ts create mode 100644 ab-testing/dictionary-deploy-lambda/tsconfig.json rename ab-testing/{scripts => }/lib/config.ts (100%) rename ab-testing/{scripts => }/lib/constants.ts (100%) rename ab-testing/{scripts => }/lib/fastly-api.test.ts (100%) rename ab-testing/{scripts => }/lib/fastly-api.ts (98%) rename ab-testing/{scripts => }/lib/fastly-subfield.test.ts (100%) rename ab-testing/{scripts => }/lib/fastly-subfield.ts (100%) rename ab-testing/{scripts => }/lib/types.ts (100%) delete mode 100644 ab-testing/riff-raff.yaml delete mode 100644 ab-testing/scripts/deploy/deploy-ab-tests.ts delete mode 100644 ab-testing/scripts/deploy/deploy-mvts.ts delete mode 100644 ab-testing/scripts/deploy/index.ts delete mode 100644 ab-testing/scripts/deploy/read-built-dictionaries.ts diff --git a/.github/workflows/ab-testing-checks.yml b/.github/workflows/ab-testing-checks.yml deleted file mode 100644 index ef6d066476a..00000000000 --- a/.github/workflows/ab-testing-checks.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Checks - -permissions: - contents: read - -on: - workflow_call: - inputs: - save_build_artifact: - description: 'Whether to save the built files as an artifact' - required: false - type: boolean - default: false - secrets: - FASTLY_AB_TESTING_CONFIG: - required: true - FASTLY_API_TOKEN: - required: true - -jobs: - checks: - runs-on: ubuntu-latest - name: Checks - defaults: - run: - working-directory: ab-testing - env: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }} - steps: - - uses: actions/checkout@v5 - - - name: Set up Node environment - uses: ./.github/actions/setup-node-env - - - name: Test - run: pnpm test - - - name: Lint - run: pnpm lint - - - name: Prettier Check - run: pnpm prettier:check - - - name: Typecheck - run: pnpm tsc - - - name: Validate - run: pnpm validate - - - name: Build - run: pnpm build - - - if: ${{ inputs.save_build_artifact }} - uses: actions/upload-artifact@v5 - with: - name: ab-testing-build - path: ab-testing/dist diff --git a/.github/workflows/ab-testing-ci-code.yml b/.github/workflows/ab-testing-ci-code.yml deleted file mode 100644 index 85b5f9ca826..00000000000 --- a/.github/workflows/ab-testing-ci-code.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: 🧪 AB testing CI (CODE) - -permissions: - contents: read - -on: - workflow_dispatch: - -jobs: - ci: - uses: ./.github/workflows/ab-testing-checks.yml - with: - save_build_artifact: true - secrets: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }} - - deploy: - name: Deploy CODE - needs: ci - uses: ./.github/workflows/ab-testing-deploy.yml - with: - stage: code - secrets: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }} diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 9aa98702ba3..a86127cb982 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -16,22 +16,149 @@ on: - '.github/workflows/ab-testing-*.yml' jobs: - ci: - name: CI - uses: ./.github/workflows/ab-testing-checks.yml - with: - save_build_artifact: true - secrets: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }} - - deploy: - name: Deploy - needs: ci - if: github.ref == 'refs/heads/main' - uses: ./.github/workflows/ab-testing-deploy.yml - with: - stage: prod - secrets: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }} + build: + runs-on: ubuntu-latest + name: Build + defaults: + run: + working-directory: ab-testing + env: + FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }} + FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }} + + steps: + - uses: actions/checkout@v5 + + - name: Set up Node environment + uses: ./.github/actions/setup-node-env + + - name: Test + run: pnpm test + + - name: Lint + run: pnpm lint + + - name: Prettier Check + run: pnpm prettier:check + + - name: Typecheck + run: pnpm tsc + + - name: Validate + run: pnpm validate + + - name: Build + run: pnpm build + + - uses: actions/upload-artifact@v5 + with: + name: ab-testing-build + path: ab-testing/dist + + build-lambda: + name: Build Lambda + runs-on: ubuntu-latest + defaults: + run: + working-directory: ab-testing/deploy-dictionary-lambda + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + + - name: Set up Node environment + uses: ./.github/actions/setup-node-env + + - name: Build Lambda + run: pnpm build + + - name: Save build + uses: actions/upload-artifact@v5 + with: + name: ab-testing-lambda-build + path: ab-testing/deploy-dictionary-lambda/dist + + build-ui: + name: UI build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ab-testing/frontend + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + + - name: Set up Node environment + uses: ./.github/actions/setup-node-env + + - name: Build UI + run: pnpm build + + - name: Save build + uses: actions/upload-artifact@v5 + with: + name: ui-build + path: ab-testing/frontend/output/ab-tests.html + if-no-files-found: error + + riff-raff: + runs-on: ubuntu-latest + needs: [build, build-ui, build-lambda] + permissions: + id-token: write + contents: read + pull-requests: write + defaults: + run: + working-directory: ab-testing + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Node + uses: ./.github/actions/setup-node-env + + - name: Fetch build + uses: actions/download-artifact@v6.0.0 + with: + name: ab-testing-build + + - name: Fetch UI build + uses: actions/download-artifact@v6.0.0 + with: + name: ui-build + path: ab-testing/frontend/output/ab-tests.html + + - name: Fetch Lambda build + uses: actions/download-artifact@v6.0.0 + with: + name: ab-testing-lambda-build + path: ab-testing/deploy-dictionary-lambda/dist + + - name: CDK Test + run: pnpm cdk:test + + - name: CDK synth + run: pnpm cdk:synth + + - name: Riff-Raff Upload + uses: guardian/actions-riff-raff@v4.1.9 + with: + roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} + githubToken: ${{ secrets.GITHUB_TOKEN }} + projectName: dotcom:ab-testing + configPath: ab-testing/cdk.out/riff-raff.yaml + contentDirectories: | + ab-testing: + - ab-testing/dist + ab-testing-deploy-dictionary-lambda: + - ab-testing/deploy-dictionary-lambda/dist + admin/ab-testing: + - ab-testing/frontend/output/ab-tests.html + cdk.out: + - ab-testing/cdk.out diff --git a/.github/workflows/ab-testing-deploy.yml b/.github/workflows/ab-testing-deploy.yml deleted file mode 100644 index 39bd334df11..00000000000 --- a/.github/workflows/ab-testing-deploy.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: 🧪 Deploy AB Testing Config - -permissions: - contents: read - -on: - workflow_call: - inputs: - stage: - description: 'Stage to deploy to (e.g., code, prod)' - required: true - type: string - secrets: - FASTLY_AB_TESTING_CONFIG: - required: true - FASTLY_API_TOKEN: - required: true - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - defaults: - run: - working-directory: ab-testing - steps: - - uses: actions/checkout@v5 - - - name: Set up Node environment - uses: ./.github/actions/setup-node-env - - - name: Download build artifact - uses: actions/download-artifact@v6.0.0 - with: - name: ab-testing-build - path: ab-testing/dist - - - name: Deploy - run: pnpm run deploy - env: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }} diff --git a/.github/workflows/ab-testing-ui.yml b/.github/workflows/ab-testing-ui.yml deleted file mode 100644 index 6ace87e1a3c..00000000000 --- a/.github/workflows/ab-testing-ui.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: 🧪 AB testing UI -on: - pull_request: - paths: - - 'ab-testing/**' - - '.github/workflows/ab-testing-*.yml' - push: - branches: - - main - paths: - - 'ab-testing/**' - - '.github/workflows/ab-testing-*.yml' - -jobs: - build-ui: - name: AB testing UI build - runs-on: ubuntu-latest - defaults: - run: - working-directory: ab-testing/frontend - permissions: - contents: read - steps: - - uses: actions/checkout@v5 - - - name: Set up Node environment - uses: ./.github/actions/setup-node-env - - - name: Build UI - run: pnpm build - - - name: Save build - uses: actions/upload-artifact@v5 - with: - name: ui-build - path: ab-testing/frontend/output/ab-tests.html - if-no-files-found: error - - riff-raff: - name: AB testing Riffraff upload - runs-on: ubuntu-latest - needs: build-ui - permissions: - id-token: write - contents: read - pull-requests: write - - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Set up Node - uses: ./.github/actions/setup-node-env - - - name: Fetch build - uses: actions/download-artifact@v6.0.0 - with: - name: ui-build - path: output/ab-tests.html - - - name: Riff-Raff Upload - uses: guardian/actions-riff-raff@v4.1.9 - with: - roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} - githubToken: ${{ secrets.GITHUB_TOKEN }} - projectName: dotcom:ab-testing - configPath: ab-testing/riff-raff.yaml - contentDirectories: | - admin/ab-testing: - - output/ab-tests.html diff --git a/ab-testing/.gitignore b/ab-testing/.gitignore new file mode 100644 index 00000000000..b5b74b15f3e --- /dev/null +++ b/ab-testing/.gitignore @@ -0,0 +1 @@ +cdk.out diff --git a/ab-testing/cdk.json b/ab-testing/cdk.json new file mode 100644 index 00000000000..b4b4549bcd6 --- /dev/null +++ b/ab-testing/cdk.json @@ -0,0 +1,7 @@ +{ + "app": "node cdk/bin/cdk.ts", + "context": { + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true" + } +} diff --git a/ab-testing/cdk/bin/cdk.ts b/ab-testing/cdk/bin/cdk.ts new file mode 100644 index 00000000000..9d2f2caab19 --- /dev/null +++ b/ab-testing/cdk/bin/cdk.ts @@ -0,0 +1,66 @@ +import "source-map-support/register.js"; +import { RiffRaffYamlFile } from "@guardian/cdk/lib/riff-raff-yaml-file/index.js"; +import { App } from "aws-cdk-lib"; +import { DictionaryDeployLambda } from "../lib/dictionaryDeployLambda.ts"; + +const app = new App(); + +const appName = "ab-testing-deploy"; + +new DictionaryDeployLambda(app, "DictionaryDeployLambdaCode", { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", + }, + app: appName, +}); + +new DictionaryDeployLambda(app, "DictionaryDeployLambdaProd", { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", + }, + app: appName, +}); + +const riffRaff = new RiffRaffYamlFile(app); +const { + riffRaffYaml: { deployments }, +} = riffRaff; + +const abTestingArtifactDeployment = "ab-testing-dictionary-artifact"; + +deployments.set(abTestingArtifactDeployment, { + app: abTestingArtifactDeployment, + contentDirectory: "dictionary-deploy-lambda/artifacts", + type: "aws-s3", + regions: new Set(["eu-west-1"]), + stacks: new Set(["frontend"]), + parameters: { + bucketSsmKey: "/account/services/dotcom-store.bucket", + prefixStack: false, + publicReadAcl: false, + }, +}); + +deployments.set("ab-testing-ui-artifact", { + app: "ab-testing-ui-artifact", + contentDirectory: "admin/ab-testing", + type: "aws-s3", + regions: new Set(["eu-west-1"]), + stacks: new Set(["frontend"]), + parameters: { + bucketSsmKey: "/account/services/dotcom-store.bucket", + cacheControl: "public, max-age=315360000", + prefixStack: false, + publicReadAcl: false, + }, +}); + +deployments + .get(`lambda-update-eu-west-1-frontend-${appName}`) + ?.dependencies?.push(abTestingArtifactDeployment); + +riffRaff.synth(); diff --git a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap new file mode 100644 index 00000000000..693b8d4b4a3 --- /dev/null +++ b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap @@ -0,0 +1,597 @@ +exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "SsmParameterValueabtestingdeployCODEfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/ab-testing-deploy/CODE/fastly-api-token" + }, + "SsmParameterValueabtestingdeployCODEfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/ab-testing-deploy/CODE/fastly-ab-testing-config" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "ID5BatonLambdaServiceRole5E3112D1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ] + } + }, + "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/CODE/ab-testing-deploy/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deploy" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deploy/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "Roles": [ + { + "Ref": "ID5BatonLambdaServiceRole5E3112D1" + } + ] + } + }, + "ID5BatonLambda41DA535C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/CODE/ab-testing-deploy/lambda.zip" + }, + "Environment": { + "Variables": { + "FASTLY_API_TOKEN": { + "Ref": "SsmParameterValueabtestingdeployCODEfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "FASTLY_AB_TESTING_CONFIG": { + "Ref": "SsmParameterValueabtestingdeployCODEfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STAGE": "CODE", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deploy" + } + }, + "FunctionName": "ab-testing-deploy-CODE", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "ID5BatonLambdaServiceRole5E3112D1", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "ID5BatonLambdaServiceRole5E3112D1" + ] + }, + "InvokeDictionaryDeployLambda": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ID5BatonLambda41DA535C", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; + +exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "SsmParameterValueabtestingdeployPRODfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/ab-testing-deploy/PROD/fastly-api-token" + }, + "SsmParameterValueabtestingdeployPRODfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/ab-testing-deploy/PROD/fastly-ab-testing-config" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "ID5BatonLambdaServiceRole5E3112D1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ] + } + }, + "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/PROD/ab-testing-deploy/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deploy" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deploy/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "Roles": [ + { + "Ref": "ID5BatonLambdaServiceRole5E3112D1" + } + ] + } + }, + "ID5BatonLambda41DA535C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/PROD/ab-testing-deploy/lambda.zip" + }, + "Environment": { + "Variables": { + "FASTLY_API_TOKEN": { + "Ref": "SsmParameterValueabtestingdeployPRODfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "FASTLY_AB_TESTING_CONFIG": { + "Ref": "SsmParameterValueabtestingdeployPRODfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STAGE": "PROD", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deploy" + } + }, + "FunctionName": "ab-testing-deploy-PROD", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "ID5BatonLambdaServiceRole5E3112D1", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "ID5BatonLambdaServiceRole5E3112D1" + ] + }, + "InvokeDictionaryDeployLambda": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ID5BatonLambda41DA535C", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.test.ts b/ab-testing/cdk/lib/dictionaryDeployLambda.test.ts new file mode 100644 index 00000000000..a32400665d6 --- /dev/null +++ b/ab-testing/cdk/lib/dictionaryDeployLambda.test.ts @@ -0,0 +1,51 @@ +import { describe, it } from "node:test"; +import { snapshot } from "node:test"; +import { basename } from "path"; +import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; +import { Template } from "aws-cdk-lib/assertions"; +import { DictionaryDeployLambda } from "./dictionaryDeployLambda.ts"; + +snapshot.setResolveSnapshotPath( + () => + `${import.meta.dirname}/__snapshots__/${basename( + import.meta.filename, + )}.snap`, +); + +void describe("The ID5 Baton Lambda stack", () => { + void it("matches the CODE snapshot", ({ assert }) => { + const app = new GuRoot(); + const stack = new DictionaryDeployLambda( + app, + "DictionaryDeployLambda", + { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", + }, + app: "ab-testing-deploy", + }, + ); + const template = Template.fromStack(stack); + assert.snapshot(template.toJSON()); + }); + + void it("matches the PROD snapshot", ({ assert }) => { + const app = new GuRoot(); + const stack = new DictionaryDeployLambda( + app, + "DictionaryDeployLambda", + { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", + }, + app: "ab-testing-deploy", + }, + ); + const template = Template.fromStack(stack); + assert.snapshot(template.toJSON()); + }); +}); diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.ts b/ab-testing/cdk/lib/dictionaryDeployLambda.ts new file mode 100644 index 00000000000..760680e8650 --- /dev/null +++ b/ab-testing/cdk/lib/dictionaryDeployLambda.ts @@ -0,0 +1,58 @@ +import type { GuStackProps } from "@guardian/cdk/lib/constructs/core/stack.js"; +import { GuStack } from "@guardian/cdk/lib/constructs/core/stack.js"; +import { GuLambdaFunction } from "@guardian/cdk/lib/constructs/lambda/index.js"; +import { GuS3Bucket } from "@guardian/cdk/lib/constructs/s3/index.js"; +import type { App } from "aws-cdk-lib"; +import { CustomResource } from "aws-cdk-lib"; +import { Runtime } from "aws-cdk-lib/aws-lambda"; +import { StringParameter } from "aws-cdk-lib/aws-ssm"; + +type Props = GuStackProps & { + app: string; +}; + +export class DictionaryDeployLambda extends GuStack { + constructor(scope: App, id: string, props: Props) { + super(scope, id, props); + + const { app } = props; + + const s3Bucket = GuS3Bucket.fromBucketName( + this, + "DictionaryDeployBucket", + StringParameter.valueForStringParameter( + this, + `/account/services/dotcom-store.bucket`, + ), + ); + + const lambda = new GuLambdaFunction(this, "ID5BatonLambda", { + functionName: `${app}-${this.stage}`, + fileName: "lambda.zip", + handler: "index.handler", + app, + runtime: Runtime.NODEJS_22_X, + memorySize: 256, + environment: { + FASTLY_API_TOKEN: StringParameter.valueForStringParameter( + this, + `/${app}/${this.stage}/fastly-api-token`, + ), + FASTLY_AB_TESTING_CONFIG: + StringParameter.valueForStringParameter( + this, + `/${app}/${this.stage}/fastly-ab-testing-config`, + ), + STAGE: this.stage, + ARTIFACT_BUCKET_NAME: s3Bucket.bucketName, + }, + }); + + s3Bucket.grantRead(lambda); + + // Trigger the Lambda to run upon deployment + new CustomResource(this, "InvokeDictionaryDeployLambda", { + serviceToken: lambda.functionArn, + }); + } +} diff --git a/ab-testing/dictionary-deploy-lambda/package.json b/ab-testing/dictionary-deploy-lambda/package.json new file mode 100644 index 00000000000..d5cc1cc7152 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/package.json @@ -0,0 +1,39 @@ +{ + "name": "@guardian/dictionary-deploy-lambda", + "version": "1.0.0", + "description": "A/B test definitions and configuration", + "main": "index.ts", + "type": "module", + "scripts": { + "build": "rollup -c rollup.config.js", + "test": "node --test", + "lint": "eslint .", + "prettier:check": "prettier . --check --cache", + "prettier:fix": "prettier . --write --cache" + }, + "dependencies": { + "@rollup/plugin-json": "6.1.0", + "cfn-response": "1.0.1", + "esbuild": "0.27.0", + "rollup-plugin-esbuild": "6.2.1", + "superstruct": "2.0.2" + }, + "devDependencies": { + "@aws-sdk/client-s3": "3.931.0", + "@guardian/cdk": "62.0.1", + "@guardian/eslint-config": "12.0.1", + "@guardian/tsconfig": "1.0.1", + "@rollup/plugin-commonjs": "29.0.0", + "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-typescript": "12.3.0", + "@types/aws-lambda": "8.10.158", + "@types/cfn-response": "1.0.8", + "@types/node": "22.17.0", + "aws-cdk-lib": "2.220.0", + "aws-lambda": "1.0.7", + "eslint": "9.39.1", + "prettier": "3.0.3", + "rollup": "4.53.2", + "typescript": "5.9.3" + } +} diff --git a/ab-testing/dictionary-deploy-lambda/rollup.config.js b/ab-testing/dictionary-deploy-lambda/rollup.config.js new file mode 100644 index 00000000000..e607ad43109 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/rollup.config.js @@ -0,0 +1,45 @@ +import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import esbuild from "rollup-plugin-esbuild"; + +/** @type {import('rollup').RollupOptions} */ +const rollupConfig = { + input: `src/index.ts`, + output: { + dir: "dist", + format: "module", + preserveModules: true, + preserveModulesRoot: "src", + sourcemap: true, + entryFileNames: (chunkInfo) => { + if (chunkInfo.name.includes("node_modules")) { + // Simplify node_modules paths, if it's in node_modules without a package.json + // it'll be assumed to be a commonjs module, which is incorrect and causes issues. + return ( + chunkInfo.name.replace( + /node_modules\/\.pnpm\/.*\/node_modules/, + "external", + ) + ".js" + ); + } + + return "[name].js"; + }, + }, + external: ["@aws-sdk/*"], + plugins: [ + commonjs(), + nodeResolve({ + preferBuiltins: true, + exportConditions: ["node"], + resolveOnly: (moduleName) => !moduleName.startsWith("@aws-sdk"), + }), + json(), + esbuild({ + include: /\.[jt]s?$/, + }), + ], +}; + +export default rollupConfig; diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts b/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts new file mode 100644 index 00000000000..83bddb1ff09 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts @@ -0,0 +1,65 @@ +import { + calculateUpdates, + getDictionaryItems, + updateDictionaryItems, + verifyDictionaryName, +} from "../../lib/fastly-api.ts"; +import type { KeyValue } from "./fetch-artifact.ts"; + +/** + * Deploys key-value pairs to a Fastly edge dictionary. + * Uses `calculateUpdates` to determine necessary CRUD operations. + * + * @param config Configuration for the dictionary deployment. + * @param keyValues An array of key-value pairs to deploy to the dictionary. + */ +export const deployDictionary = async ( + { + dictionaryName, + dictionaryId, + serviceId, + activeVersion, + }: { + dictionaryName: string; + dictionaryId: string; + serviceId: string; + activeVersion: number; + }, + keyValues: KeyValue[], +) => { + await verifyDictionaryName({ + serviceId, + activeVersion, + dictionaryName, + dictionaryId, + }); + + const currentKeyValues = await getDictionaryItems({ + dictionaryId, + }); + + const updates = calculateUpdates(keyValues, currentKeyValues); + + if (updates.length === 0) { + console.log(`No key-values need updating in '${dictionaryName}'`); + } else { + Map.groupBy(updates, (item) => item.op).forEach((items, op) => { + console.log( + `Performing ${items.length} ${op} operations in '${dictionaryName}'`, + ); + }); + + console.log( + `Performing ${updates.length} total operations in '${dictionaryName}'`, + ); + + const response = await updateDictionaryItems({ + dictionaryId, + items: updates, + }); + + if (response.status !== "ok") { + throw new Error(`Failed to update mvt groups dictionary`); + } + } +}; diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts new file mode 100644 index 00000000000..4203d087b23 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -0,0 +1,52 @@ +import { deployDictionary } from "./deploy-dictionary.ts"; +import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; + +const ARTIFACT_BUCKET_NAME = process.env.ARTIFACT_BUCKET_NAME ?? ""; +const STAGE = process.env.STAGE ?? "CODE"; + +const CONFIG_PREFIX = `/${STAGE}/config/ab-tests`; + +type ServiceInfo = { + activeVersion: { number: number }; + serviceId: string; +}; + +type ArtifactInfo = { + artifact: string; + dictionaryName: string; + dictionaryId: string; +}; + +/** + * Fetches dictionary artifacts from S3 and deploys them to Fastly dictionaries in parallel. + * @param serviceInfo The Fastly service information including active version and service ID. + * @param deployments An array of artifact deployment information. + */ +export const fetchAndDeployArtifacts = async ( + { activeVersion, serviceId }: ServiceInfo, + deployments: ArtifactInfo[], +) => { + try { + await Promise.all( + deployments.map(({ artifact, dictionaryName, dictionaryId }) => + fetchDictionaryArtifact( + ARTIFACT_BUCKET_NAME, + `${CONFIG_PREFIX}/${artifact}`, + ).then((abTestGroups) => + deployDictionary( + { + serviceId, + activeVersion: activeVersion.number, + dictionaryName, + dictionaryId, + }, + abTestGroups, + ), + ), + ), + ); + } catch (error) { + console.error("Error in fetchAndDeployArtifacts:", error); + throw error; + } +}; diff --git a/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts b/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts new file mode 100644 index 00000000000..ebdf85a109e --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts @@ -0,0 +1,42 @@ +import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import type { Infer } from "superstruct"; +import { array, create, object, string } from "superstruct"; + +const fastlyKVStruct = object({ + item_key: string(), + item_value: string(), +}); + +type KeyValue = Infer; + +/** + * Fetches the dictionary artifact from the given s3 location, using the AWS SDK. + * @param Bucket The S3 bucket name. + * @param Key The S3 object key. + * @returns A promise that resolves to the artifact JSON. + */ +const fetchDictionaryArtifact = async ( + Bucket: string, + Key: string, +): Promise => { + const s3Client = new S3Client({ region: "eu-west-1" }); + + const getObjectCommand = new GetObjectCommand({ + Bucket, + Key, + }); + const response = await s3Client.send(getObjectCommand); + + if (!response.Body) { + throw new Error("No body found in S3 object response"); + } + + const bodyString = await response.Body.transformToString(); + const parsed = JSON.parse(bodyString) as unknown; + + const result = create(parsed, array(fastlyKVStruct)); + + return result; +}; + +export { fetchDictionaryArtifact, type KeyValue }; diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts new file mode 100644 index 00000000000..4e07981f242 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -0,0 +1,61 @@ +import type { Handler } from "aws-cdk-lib/aws-lambda"; +import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; +import { send } from "cfn-response"; +import { + abTestsDictionaryId, + abTestsDictionaryName, + mvtDictionaryId, + mvtDictionaryName, + serviceId, + serviceName, +} from "../../lib/config.ts"; +import { getService } from "../../lib/fastly-api.ts"; +import { fetchAndDeployArtifacts } from "./deploy.ts"; + +export const handler: Handler = async ( + event: CloudFormationCustomResourceEvent, + context: Context, +): Promise => { + const service = await getService(serviceId); + if (service.name !== serviceName) { + throw new Error( + `Service ID ${serviceId} does not match the expected service name ${serviceName}`, + ); + } + + const activeVersion = service.versions.find( + (v: { active: boolean }) => v.active, + ); + + if (!activeVersion) { + throw new Error(`No active version found for service ${service.name}`); + } + + if (event.RequestType === "Create" || event.RequestType === "Update") { + try { + await fetchAndDeployArtifacts( + { + activeVersion, + serviceId, + }, + [ + { + artifact: "ab-test-groups.json", + dictionaryName: abTestsDictionaryName, + dictionaryId: abTestsDictionaryId, + }, + { + artifact: "mvt-groups.json", + dictionaryName: mvtDictionaryName, + dictionaryId: mvtDictionaryId, + }, + ], + ); + + send(event, context, "SUCCESS"); + } catch (error) { + console.error("Error deploying dictionaries:", error); + send(event, context, "FAILED"); + } + } +}; diff --git a/ab-testing/dictionary-deploy-lambda/tsconfig.json b/ab-testing/dictionary-deploy-lambda/tsconfig.json new file mode 100644 index 00000000000..6f69de8a60b --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@guardian/tsconfig/tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "noEmit": true + }, + "exclude": ["node_modules"] +} diff --git a/ab-testing/scripts/lib/config.ts b/ab-testing/lib/config.ts similarity index 100% rename from ab-testing/scripts/lib/config.ts rename to ab-testing/lib/config.ts diff --git a/ab-testing/scripts/lib/constants.ts b/ab-testing/lib/constants.ts similarity index 100% rename from ab-testing/scripts/lib/constants.ts rename to ab-testing/lib/constants.ts diff --git a/ab-testing/scripts/lib/fastly-api.test.ts b/ab-testing/lib/fastly-api.test.ts similarity index 100% rename from ab-testing/scripts/lib/fastly-api.test.ts rename to ab-testing/lib/fastly-api.test.ts diff --git a/ab-testing/scripts/lib/fastly-api.ts b/ab-testing/lib/fastly-api.ts similarity index 98% rename from ab-testing/scripts/lib/fastly-api.ts rename to ab-testing/lib/fastly-api.ts index c00b2115042..5cc813f2b88 100644 --- a/ab-testing/scripts/lib/fastly-api.ts +++ b/ab-testing/lib/fastly-api.ts @@ -96,9 +96,11 @@ const getService = async (serviceId: string) => { const getDictionary = async ({ activeVersion, dictionaryName, + serviceId, }: { activeVersion: number; dictionaryName: string; + serviceId: string; }) => { const dictionary = await fetchFromFastly( `${FASTLY_API_BASE_URL}/${serviceId}/version/${activeVersion}/dictionary/${dictionaryName}`, @@ -239,16 +241,19 @@ const calculateUpdates = ( * */ const verifyDictionaryName = async ({ + serviceId, activeVersion, dictionaryName, dictionaryId, }: { + serviceId: string; activeVersion: number; dictionaryName: string; dictionaryId: string; }) => { const dictionary = await getDictionary({ - activeVersion: activeVersion, + serviceId, + activeVersion, dictionaryName, }); @@ -294,6 +299,7 @@ export { getDictionaryItems, updateMVTGroups, updateABTestGroups, + updateDictionaryItems, calculateUpdates, encodeObject, verifyDictionaryName, diff --git a/ab-testing/scripts/lib/fastly-subfield.test.ts b/ab-testing/lib/fastly-subfield.test.ts similarity index 100% rename from ab-testing/scripts/lib/fastly-subfield.test.ts rename to ab-testing/lib/fastly-subfield.test.ts diff --git a/ab-testing/scripts/lib/fastly-subfield.ts b/ab-testing/lib/fastly-subfield.ts similarity index 100% rename from ab-testing/scripts/lib/fastly-subfield.ts rename to ab-testing/lib/fastly-subfield.ts diff --git a/ab-testing/scripts/lib/types.ts b/ab-testing/lib/types.ts similarity index 100% rename from ab-testing/scripts/lib/types.ts rename to ab-testing/lib/types.ts diff --git a/ab-testing/package.json b/ab-testing/package.json index 2023d407d3c..940b15202bb 100644 --- a/ab-testing/package.json +++ b/ab-testing/package.json @@ -9,20 +9,29 @@ "deploy": "node scripts/deploy/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json", "build": "node scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json", "build-ui": "pnpm --filter @guardian/ab-testing-frontend run build", + "lambda:build": "pnpm --filter @guardian/ab-testing-lambda run build", "test": "node --test", "lint": "eslint .", "prettier:check": "prettier . --check --cache", - "prettier:fix": "prettier . --write --cache" + "prettier:fix": "prettier . --write --cache", + "cdk:test": "node --test --test-reporter spec cdk/*/*.test.ts", + "cdk:test-update": "node --test --test-reporter spec --test-update-snapshots cdk/*/*.test.ts", + "cdk:synth": "cdk synth --path-metadata false --version-reporting false", + "cdk:diff": "cdk diff --path-metadata false --version-reporting false" }, "dependencies": { "superstruct": "2.0.2" }, "devDependencies": { + "@aws-sdk/client-s3": "3.931.0", + "@guardian/cdk": "62.0.1", "@guardian/eslint-config": "12.0.1", "@guardian/tsconfig": "1.0.1", "@types/node": "22.17.0", + "aws-cdk-lib": "2.220.0", "eslint": "9.39.1", "prettier": "3.0.3", + "source-map-support": "0.5.21", "typescript": "5.9.3" } } diff --git a/ab-testing/riff-raff.yaml b/ab-testing/riff-raff.yaml deleted file mode 100644 index 086024eb7e2..00000000000 --- a/ab-testing/riff-raff.yaml +++ /dev/null @@ -1,13 +0,0 @@ -regions: [eu-west-1] -stacks: [frontend] -allowedStages: - - CODE - - PROD -deployments: - admin/ab-testing: - type: aws-s3 - parameters: - bucketSsmKey: /account/services/dotcom-store.bucket - cacheControl: public, max-age=315360000 - prefixStack: false - publicReadAcl: false diff --git a/ab-testing/scripts/build/build-ab-tests-dict.ts b/ab-testing/scripts/build/build-ab-tests-dict.ts index 9c56e3493f8..036959112cf 100644 --- a/ab-testing/scripts/build/build-ab-tests-dict.ts +++ b/ab-testing/scripts/build/build-ab-tests-dict.ts @@ -1,5 +1,5 @@ +import { stringifyFastlySubfield } from "../../lib/fastly-subfield.ts"; import type { ABTest } from "../../types.ts"; -import { stringifyFastlySubfield } from "../lib/fastly-subfield.ts"; const buildABTestGroupKeyValues = (tests: ABTest[]) => tests.flatMap((test) => diff --git a/ab-testing/scripts/build/calculate-mvt-updates.test.ts b/ab-testing/scripts/build/calculate-mvt-updates.test.ts index c2a905a56a5..b19fe0ac74a 100644 --- a/ab-testing/scripts/build/calculate-mvt-updates.test.ts +++ b/ab-testing/scripts/build/calculate-mvt-updates.test.ts @@ -1,11 +1,11 @@ import { deepEqual, equal, throws } from "node:assert"; import { test } from "node:test"; -import type { ABTest } from "../../types.ts"; import type { AllSpace, AudienceSpace, FastlyTestParams, -} from "../lib/types.ts"; +} from "../../lib/types.ts"; +import type { ABTest } from "../../types.ts"; import { calculateAllSpaceUpdates, calculateSpaceUpdates, diff --git a/ab-testing/scripts/build/calculate-mvt-updates.ts b/ab-testing/scripts/build/calculate-mvt-updates.ts index 40aca5581a6..ba0b1c35a28 100644 --- a/ab-testing/scripts/build/calculate-mvt-updates.ts +++ b/ab-testing/scripts/build/calculate-mvt-updates.ts @@ -1,10 +1,10 @@ -import type { ABTest } from "../../types.ts"; -import { AUDIENCE_SPACES, MVT_COUNT } from "../lib/constants.ts"; +import { AUDIENCE_SPACES, MVT_COUNT } from "../../lib/constants.ts"; import type { AllSpace, AudienceSpace, FastlyTestParams, -} from "../lib/types.ts"; +} from "../../lib/types.ts"; +import type { ABTest } from "../../types.ts"; import { TestGroupMVTManager } from "./test-group-mvt-manager.ts"; const getTestGroupName = ( diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/scripts/build/index.ts index e97db9d42b7..d6500486902 100644 --- a/ab-testing/scripts/build/index.ts +++ b/ab-testing/scripts/build/index.ts @@ -2,8 +2,8 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { parseArgs } from "node:util"; import { activeABtests } from "../../abTests.ts"; -import { getMVTGroupsFromDictionary } from "../lib/fastly-api.ts"; -import { parseMVTValue, stringifyMVTValue } from "../lib/fastly-subfield.ts"; +import { getMVTGroupsFromDictionary } from "../../lib/fastly-api.ts"; +import { parseMVTValue, stringifyMVTValue } from "../../lib/fastly-subfield.ts"; import { buildABTestGroupKeyValues } from "./build-ab-tests-dict.ts"; import { calculateAllSpaceUpdates } from "./calculate-mvt-updates.ts"; diff --git a/ab-testing/scripts/build/test-group-mvt-manager.test.ts b/ab-testing/scripts/build/test-group-mvt-manager.test.ts index 004c61c5622..a87909c77d5 100644 --- a/ab-testing/scripts/build/test-group-mvt-manager.test.ts +++ b/ab-testing/scripts/build/test-group-mvt-manager.test.ts @@ -1,6 +1,6 @@ import { deepEqual, equal, throws } from "node:assert"; import test from "node:test"; -import type { AudienceSpace } from "../lib/types.ts"; +import type { AudienceSpace } from "../../lib/types.ts"; import { TestGroupMVTManager } from "./test-group-mvt-manager.ts"; // Helper function to create mock AudienceSpace diff --git a/ab-testing/scripts/build/test-group-mvt-manager.ts b/ab-testing/scripts/build/test-group-mvt-manager.ts index 37aa5f2e49d..296fff1cd78 100644 --- a/ab-testing/scripts/build/test-group-mvt-manager.ts +++ b/ab-testing/scripts/build/test-group-mvt-manager.ts @@ -1,5 +1,5 @@ -import { MVT_COUNT } from "../lib/constants.ts"; -import type { AudienceSpace } from "../lib/types.ts"; +import { MVT_COUNT } from "../../lib/constants.ts"; +import type { AudienceSpace } from "../../lib/types.ts"; /** * A class to manage MVTs for test groups in a test space. diff --git a/ab-testing/scripts/deploy/deploy-ab-tests.ts b/ab-testing/scripts/deploy/deploy-ab-tests.ts deleted file mode 100644 index 6cfaba9cf1c..00000000000 --- a/ab-testing/scripts/deploy/deploy-ab-tests.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - calculateUpdates, - getABTestGroupsFromDictionary, - updateABTestGroups, -} from "../lib/fastly-api.ts"; -import { getUpdatedABTestGroups } from "./read-built-dictionaries.ts"; - -const deployABTests = async (filePath: string) => { - // update ab test groups first - const updatedABTestGroups = await getUpdatedABTestGroups(filePath); - const currentABTestGroups = await getABTestGroupsFromDictionary(); - - const abTestGroupUpdates = calculateUpdates( - updatedABTestGroups, - currentABTestGroups, - ); - - if (abTestGroupUpdates.length === 0) { - console.log("No ab test groups to update"); - } else { - Map.groupBy(abTestGroupUpdates, (item) => item.op).forEach( - (items, op) => { - if (op === "delete") { - console.log( - `Deleting ${items.length} ab test groups from dictionary`, - ); - } - if (op === "update") { - console.log( - `Updating ${items.length} ab test groups in dictionary`, - ); - } - if (op === "create") { - console.log( - `Creating ${items.length} ab test groups in dictionary`, - ); - } - }, - ); - - const updateABTestGroupsResponse = - await updateABTestGroups(abTestGroupUpdates); - - if (updateABTestGroupsResponse.status !== "ok") { - throw new Error(`Failed to update ab test groups dictionary`); - } - } -}; - -export { deployABTests }; diff --git a/ab-testing/scripts/deploy/deploy-mvts.ts b/ab-testing/scripts/deploy/deploy-mvts.ts deleted file mode 100644 index ceb0bb1f777..00000000000 --- a/ab-testing/scripts/deploy/deploy-mvts.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - calculateUpdates, - getMVTGroupsFromDictionary, - updateMVTGroups, -} from "../lib/fastly-api.ts"; -import { getMVTGroups } from "./read-built-dictionaries.ts"; - -const deployMVTs = async (filePath: string) => { - // update mvt groups - const mvtGroups = await getMVTGroups(filePath); - const currentMVTGroups = await getMVTGroupsFromDictionary(); - - console.log(`Current MVT Groups: ${currentMVTGroups.length}`); - console.log(`New MVT Groups: ${mvtGroups.length}`); - - const mvtGroupUpdates = calculateUpdates(mvtGroups, currentMVTGroups); - - if (mvtGroupUpdates.length === 0) { - console.log("No mvt groups to update"); - } else { - Map.groupBy(mvtGroupUpdates, (item) => item.op).forEach((items, op) => { - if (op === "delete") { - console.log( - `Deleting ${items.length} mvt groups from dictionary`, - ); - } - if (op === "update") { - console.log( - `Updating ${items.length} mvt groups in dictionary`, - ); - } - if (op === "create") { - console.log( - `Creating ${items.length} mvt groups in dictionary`, - ); - } - }); - - console.log( - `Performing ${mvtGroupUpdates.length} mvt groups dictionary operations`, - ); - - const updateMVTGroupsResponse = await updateMVTGroups(mvtGroupUpdates); - - if (updateMVTGroupsResponse.status !== "ok") { - throw new Error(`Failed to update mvt groups dictionary`); - } - } -}; - -export { deployMVTs }; diff --git a/ab-testing/scripts/deploy/index.ts b/ab-testing/scripts/deploy/index.ts deleted file mode 100644 index 6d70391f3a1..00000000000 --- a/ab-testing/scripts/deploy/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { parseArgs } from "node:util"; -import { - abTestsDictionaryId, - abTestsDictionaryName, - mvtDictionaryId, - mvtDictionaryName, - serviceId, - serviceName, -} from "../lib/config.ts"; -import { getService, verifyDictionaryName } from "../lib/fastly-api.ts"; -import { deployABTests } from "./deploy-ab-tests.ts"; -import { deployMVTs } from "./deploy-mvts.ts"; - -const flags = parseArgs({ - args: process.argv.slice(2), - options: { - mvts: { - type: "string", - short: "m", - }, - "ab-tests": { - type: "string", - short: "a", - }, - }, -}).values; - -if (!flags["mvts"] || !flags["ab-tests"]) { - console.error( - "Please provide the path to the mvt and ab test groups dictionaries", - ); - process.exit(1); -} - -const service = await getService(serviceId); -if (service.name !== serviceName) { - throw new Error( - `Service ID ${serviceId} does not match the expected service name ${serviceName}`, - ); -} - -const activeVersion = service.versions.find( - (v: { active: boolean }) => v.active, -); - -if (!activeVersion) { - throw new Error(`No active version found for service ${service.name}`); -} - -// Verify that the service ID and dictionary names match the expected values -await Promise.all([ - verifyDictionaryName({ - activeVersion: activeVersion.number, - dictionaryName: mvtDictionaryName, - dictionaryId: mvtDictionaryId, - }), - verifyDictionaryName({ - activeVersion: activeVersion.number, - dictionaryName: abTestsDictionaryName, - dictionaryId: abTestsDictionaryId, - }), -]); - -await Promise.all([ - deployABTests(flags["ab-tests"]), - deployMVTs(flags["mvts"]), -]); - -console.log("Successfully updated ab test groups and mvt groups dictionaries"); diff --git a/ab-testing/scripts/deploy/read-built-dictionaries.ts b/ab-testing/scripts/deploy/read-built-dictionaries.ts deleted file mode 100644 index 56bd59c15e6..00000000000 --- a/ab-testing/scripts/deploy/read-built-dictionaries.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { readFile } from "fs/promises"; -import { array, assert, object, string } from "superstruct"; - -const fastlyKVStruct = object({ - item_key: string(), - item_value: string(), -}); - -const getUpdatedABTestGroups = async (file: string) => { - const updatedABTestGroups = JSON.parse( - await readFile(file, { - encoding: "utf-8", - }), - ) as unknown; - - assert(updatedABTestGroups, array(fastlyKVStruct)); - - return updatedABTestGroups; -}; - -const getMVTGroups = async (file: string) => { - const mvtGroups = JSON.parse( - await readFile(file, { - encoding: "utf-8", - }), - ) as unknown; - - assert(mvtGroups, array(fastlyKVStruct)); - return mvtGroups; -}; - -export { getUpdatedABTestGroups, getMVTGroups }; diff --git a/ab-testing/scripts/validation/limitServerSide.test.ts b/ab-testing/scripts/validation/limitServerSide.test.ts index 5f8a21a5d73..e503c90e774 100644 --- a/ab-testing/scripts/validation/limitServerSide.test.ts +++ b/ab-testing/scripts/validation/limitServerSide.test.ts @@ -1,7 +1,7 @@ import { equal, throws } from "node:assert"; import test from "node:test"; +import { MAX_SERVER_SIDE_TESTS } from "../../lib/constants.ts"; import type { ABTest } from "../../types.ts"; -import { MAX_SERVER_SIDE_TESTS } from "../lib/constants.ts"; import { limitServerSideTests } from "./limitServerSide.ts"; test("limitServerSideTests - throws if the amount of tests exceeds the limit", () => { diff --git a/ab-testing/scripts/validation/limitServerSide.ts b/ab-testing/scripts/validation/limitServerSide.ts index 7ba85bfe2d6..ddb19054961 100644 --- a/ab-testing/scripts/validation/limitServerSide.ts +++ b/ab-testing/scripts/validation/limitServerSide.ts @@ -1,5 +1,5 @@ +import { MAX_SERVER_SIDE_TESTS } from "../../lib/constants.ts"; import type { ABTest } from "../../types.ts"; -import { MAX_SERVER_SIDE_TESTS } from "../lib/constants.ts"; export function limitServerSideTests(tests: ABTest[]) { const serverSideTests = tests.filter((test) => test.type === "server"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdba7968488..aef98992b8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,12 @@ importers: specifier: 2.0.2 version: 2.0.2 devDependencies: + '@aws-sdk/client-s3': + specifier: 3.931.0 + version: 3.931.0 + '@guardian/cdk': + specifier: 62.0.1 + version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) '@guardian/eslint-config': specifier: 12.0.1 version: 12.0.1(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) @@ -50,12 +56,18 @@ importers: '@types/node': specifier: 22.17.0 version: 22.17.0 + aws-cdk-lib: + specifier: 2.220.0 + version: 2.220.0(constructs@10.4.2) eslint: specifier: 9.39.1 version: 9.39.1 prettier: specifier: 3.0.3 version: 3.0.3 + source-map-support: + specifier: 0.5.21 + version: 0.5.21 typescript: specifier: 5.9.3 version: 5.9.3 @@ -397,7 +409,7 @@ importers: version: 10.0.7(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) '@storybook/addon-docs': specifier: 10.0.7 - version: 10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.52.4)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) + version: 10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) '@storybook/addon-webpack5-compiler-swc': specifier: 4.0.2 version: 4.0.2(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(webpack@5.102.1) @@ -571,10 +583,10 @@ importers: version: 8.57.1 eslint-config-airbnb-base: specifier: 15.0.0 - version: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1))(eslint@8.57.1) + version: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) eslint-config-airbnb-typescript: specifier: 17.0.0 - version: 17.0.0(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3))(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1))(eslint@8.57.1) + version: 17.0.0(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3))(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-custom-elements: specifier: 0.0.8 version: 0.0.8(eslint@8.57.1) @@ -583,7 +595,7 @@ importers: version: 6.7.1(eslint@8.57.1) eslint-plugin-jsx-expressions: specifier: 1.3.1 - version: 1.3.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) + version: 1.3.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) eslint-plugin-mocha: specifier: 10.1.0 version: 10.1.0(eslint@8.57.1) @@ -851,8 +863,8 @@ packages: resolution: {integrity: sha512-eLLKMwORBJ32YyKRo2LhWtYAYoWdnEPZSo6CyD4QUcsOosvPGdJgz4s13O3AmC60Sn43X5g3Zc4vgKvhZCfkUw==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-s3@3.842.0': - resolution: {integrity: sha512-T5Rh72Rcq1xIaM8KkTr1Wpr7/WPCYO++KrM+/Em0rq2jxpjMMhj77ITpgH7eEmNxWmwIndTwqpgfmbpNfk7Gbw==} + '@aws-sdk/client-s3@3.931.0': + resolution: {integrity: sha512-p+ZSRvmylk/pNImGDvLt3lOkILOexNcYvsCjvN2TR9X8RvxvPURISVp2qdGKdwUr/zkshteg1x/30GYlcTKs5g==} engines: {node: '>=18.0.0'} '@aws-sdk/client-ssm@3.621.0': @@ -885,6 +897,10 @@ packages: resolution: {integrity: sha512-oEWXhe2RHiSPKxhrq1qp7M4fxOsxMIJc4d75z8tTLLm5ujlmTZYU3kd0l2uBBaZSlbkrMiefntT6XrGint1ibw==} engines: {node: '>=18.0.0'} + '@aws-sdk/client-sso@3.931.0': + resolution: {integrity: sha512-GM/CARsIUQGEspM9VhZaftFVXnNtFNUUXjpM1ePO4CHk1J/VFvXcsQr3SHWIs0F4Ll6pvy5LpcRlWW5pK7T4aQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/client-sts@3.621.0': resolution: {integrity: sha512-707uiuReSt+nAx6d0c21xLjLm2lxeKc7padxjv92CIrIocnQSlJPxSCM7r5zBhwiahJA6MNQwmTl2xznU67KgA==} engines: {node: '>=16.0.0'} @@ -901,6 +917,10 @@ packages: resolution: {integrity: sha512-b/FVNyPxZMmBp+xDwANDgR6o5Ehh/RTY9U/labH56jJpte196Psru/FmQULX3S6kvIiafQA9JefWUq81SfWVLg==} engines: {node: '>=18.0.0'} + '@aws-sdk/core@3.931.0': + resolution: {integrity: sha512-l/b6AQbto4TuXL2FIm7Z+tbVjrp0LN7ESm97Sf3nneB0vjKtB6R0TS/IySzCYMgyOC3Hxz+Ka34HJXZk9eXTFw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-cognito-identity@3.621.0': resolution: {integrity: sha512-Q+3awvTVJSqIGRjCUQflRwKPKlZ0TfmL3EQHgFLhZZrToeBapEA62+FY+T70aTKAZZZZprlvYeFPtBloNd5ziA==} engines: {node: '>=16.0.0'} @@ -921,6 +941,10 @@ packages: resolution: {integrity: sha512-Os8I5XtTLBBVyHJLxrEB06gSAZeFMH2jVoKhAaFybjOTiV7wnjBgjvWjRfStnnXs7p9d+vc/gd6wIZHjony5YQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-env@3.931.0': + resolution: {integrity: sha512-dTNBpkKXyBdcpEjyfgkE/EFU/0NRoukLs+Pj0S8K1Dg216J9uIijpi6CaBBN+HvnaTlEItm2tzXiJpPVI+TqHQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-http@3.621.0': resolution: {integrity: sha512-/jc2tEsdkT1QQAI5Dvoci50DbSxtJrevemwFsm0B73pwCcOQZ5ZwwSdVqGsPutzYzUVx3bcXg3LRL7jLACqRIg==} engines: {node: '>=16.0.0'} @@ -933,6 +957,10 @@ packages: resolution: {integrity: sha512-3KiGsTlqMnvthv90K88Uv3SvaUbmcTShBIVWYNaHdbrhrjVRR08dm2Y6XjQILazLf1NPFkxUou1YwCWK4nae1Q==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-http@3.931.0': + resolution: {integrity: sha512-7Ge26fhMDn51BTbHgopx5+uOl4I47k15BDzYc4YT6zyjS99uycYNCA7zB500DGTTn2HK27ZDTyAyhTKZGxRxbA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.621.0': resolution: {integrity: sha512-0EWVnSc+JQn5HLnF5Xv405M8n4zfdx9gyGdpnCmAmFqEDHA8LmBdxJdpUk1Ovp/I5oPANhjojxabIW5f1uU0RA==} engines: {node: '>=16.0.0'} @@ -947,6 +975,10 @@ packages: resolution: {integrity: sha512-/8x9LKKaLGarvF1++bFEFdIvd9/djBb+HTULbJAf4JVg3tUlpHtGe7uquuZaQkQGeW4XPbcpB9RMWx5YlZkw3w==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.931.0': + resolution: {integrity: sha512-uzicpP7IHBxvAMjwGdmeke2bGTxjsKCSW7N48zuv0t0d56hmGHfcZIK5p4ry2OBJxzScp182OUAdAEG8wuSuuA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.621.0': resolution: {integrity: sha512-4JqpccUgz5Snanpt2+53hbOBbJQrSFq7E1sAAbgY6BKVQUsW5qyXqnjvSF32kDeKa5JpBl3bBWLZl04IadcPHw==} engines: {node: '>=16.0.0'} @@ -959,6 +991,10 @@ packages: resolution: {integrity: sha512-Zz5tF/U4q9ir3rfVnPLlxbhMTHjPaPv78TarspFYn9mNN7cPVXBaXVVnMNu6ypZzBdTB8M44UYo827Qcw3kouA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.931.0': + resolution: {integrity: sha512-eO8mfWNHz0dyYdVfPLVzmqXaSA3agZF/XvBO9/fRU90zCb8lKlXfgUmghGW7LhDkiv2v5uuizUiag7GsKoIcJw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.620.1': resolution: {integrity: sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==} engines: {node: '>=16.0.0'} @@ -971,6 +1007,10 @@ packages: resolution: {integrity: sha512-l1lZfHIl/z0SxXibt7wMQ2HmRIyIZjlOrT6a554xlO//y671uxPPwScVw7QW4fPIvwfmKbl8dYCwGI//AgQ0bA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.931.0': + resolution: {integrity: sha512-8Mu9r+5BUKqmKSI/WYHl5o4GeoonEb51RmoLEqG6431Uz4Y8C6gzAT69yjOJ+MwoWQ2Os37OZLOTv7SgxyOgrQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.621.0': resolution: {integrity: sha512-Kza0jcFeA/GEL6xJlzR2KFf1PfZKMFnxfGzJzl5yN7EjoGdMijl34KaRyVnfRjnCWcsUpBWKNIDk9WZVMY9yiw==} engines: {node: '>=16.0.0'} @@ -983,6 +1023,10 @@ packages: resolution: {integrity: sha512-cwc9bmomjUqPDF58THUCmEnpAIsCFV3Y9FHlQmQbMkYUm7Wlrb5E2iFrZ4WDefAHuh25R/gtj+Yo74r3gl9kbw==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.931.0': + resolution: {integrity: sha512-FP31lfMgNMDG4ZDX4NUZ+uoHWn76etcG8UWEgzZb4YOPV4M8a7gwU95iD+RBaK4lV3KvwH2tu68Hmne1qQpFqQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.621.0': resolution: {integrity: sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==} engines: {node: '>=16.0.0'} @@ -997,6 +1041,10 @@ packages: resolution: {integrity: sha512-HFQgZm1+7WisJ8tqcZkNRRmnoFO+So+L12wViVxneVJ+OclfL2vE/CoKqHTozP6+JCOKMlv6Vi61Lu6xDtKdTA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.931.0': + resolution: {integrity: sha512-hfX0Buw2+ie0FBiSFMmnXfugQc9fO0KvEojnNnzhk4utlWjZobMcUprOQ/VKUueg0Kga1b1xu8gEP6g1aEh3zw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-providers@3.621.0': resolution: {integrity: sha512-FQbC7I8ae/72ZekLBa45jWJ+Q3d+YPhc3bW/rCks6RrldM6RgLTGr8pTOPCxHl828ky10RjkBiBmVU818rliyw==} engines: {node: '>=16.0.0'} @@ -1016,20 +1064,20 @@ packages: peerDependencies: '@aws-sdk/client-dynamodb': ^3.840.0 - '@aws-sdk/middleware-bucket-endpoint@3.840.0': - resolution: {integrity: sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==} + '@aws-sdk/middleware-bucket-endpoint@3.930.0': + resolution: {integrity: sha512-cnCLWeKPYgvV4yRYPFH6pWMdUByvu2cy2BAlfsPpvnm4RaVioztyvxmQj5PmVN5fvWs5w/2d6U7le8X9iye2sA==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-endpoint-discovery@3.840.0': resolution: {integrity: sha512-IJDShY5NOg9luTE8h4o2Bm+gsPnHIU0tVWCjMz8vZMLevYjKdIsatcXiu3huTOjKSnelzC9fBHfU6KKsHmjjBQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-expect-continue@3.840.0': - resolution: {integrity: sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==} + '@aws-sdk/middleware-expect-continue@3.930.0': + resolution: {integrity: sha512-5HEQ+JU4DrLNWeY27wKg/jeVa8Suy62ivJHOSUf6e6hZdVIMx0h/kXS1fHEQNNiLu2IzSEP/bFXsKBaW7x7s0g==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.840.0': - resolution: {integrity: sha512-Kg/o2G6o72sdoRH0J+avdcf668gM1bp6O4VeEXpXwUj/urQnV5qiB2q1EYT110INHUKWOLXPND3sQAqh6sTqHw==} + '@aws-sdk/middleware-flexible-checksums@3.931.0': + resolution: {integrity: sha512-eYWwUKeEommCrrm0Ro6fGDwVO0x2bL3niOmSnHIlIdpu7ruzAGaphj+2MekCxaSPORzkZ3yheHUzV45D8Qj63A==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-host-header@3.620.0': @@ -1044,8 +1092,12 @@ packages: resolution: {integrity: sha512-F9Lqeu80/aTM6S/izZ8RtwSmjfhWjIuxX61LX+/9mxJyEkgaECRxv0chsLQsLHJumkGnXRy/eIyMLBhcTPF5vg==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-location-constraint@3.840.0': - resolution: {integrity: sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ==} + '@aws-sdk/middleware-host-header@3.930.0': + resolution: {integrity: sha512-x30jmm3TLu7b/b+67nMyoV0NlbnCVT5DI57yDrhXAPCtdgM1KtdLWt45UcHpKOm1JsaIkmYRh2WYu7Anx4MG0g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-location-constraint@3.930.0': + resolution: {integrity: sha512-QIGNsNUdRICog+LYqmtJ03PLze6h2KCORXUs5td/hAEjVP5DMmubhtrGg1KhWyctACluUH/E/yrD14p4pRXxwA==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-logger@3.609.0': @@ -1060,6 +1112,10 @@ packages: resolution: {integrity: sha512-3LJyyfs1USvRuRDla1pGlzGRtXJBXD1zC9F+eE9Iz/V5nkmhyv52A017CvKWmYoR0DM9dzjLyPOI0BSSppEaTw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.930.0': + resolution: {integrity: sha512-vh4JBWzMCBW8wREvAwoSqB2geKsZwSHTa0nSt0OMOLp2PdTYIZDi0ZiVMmpfnjcx9XbS6aSluLv9sKx4RrG46A==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.620.0': resolution: {integrity: sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==} engines: {node: '>=16.0.0'} @@ -1072,12 +1128,16 @@ packages: resolution: {integrity: sha512-m/oLz0EoCy+WoIVBnXRXJ4AtGpdl0kPE7U+VH9TsuUzHgxY1Re/176Q1HWLBRVlz4gr++lNsgsMWEC+VnAwMpw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.840.0': - resolution: {integrity: sha512-rOUji7CayWN3O09zvvgLzDVQe0HiJdZkxoTS6vzOS3WbbdT7joGdVtAJHtn+x776QT3hHzbKU5gnfhel0o6gQA==} + '@aws-sdk/middleware-recursion-detection@3.930.0': + resolution: {integrity: sha512-gv0sekNpa2MBsIhm2cjP3nmYSfI4nscx/+K9u9ybrWZBWUIC4kL2sV++bFjjUz4QxUIlvKByow3/a9ARQyCu7Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-ssec@3.840.0': - resolution: {integrity: sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==} + '@aws-sdk/middleware-sdk-s3@3.931.0': + resolution: {integrity: sha512-uWF78ht8Wgxljn6y0cEcIWfbeTVnJ0cE1Gha9ScCqscmuBCpHuFMSd/p53w3whoDhpQL3ln9mOyY3tfST/NUQA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-ssec@3.930.0': + resolution: {integrity: sha512-N2/SvodmaDS6h7CWfuapt3oJyn1T2CBz0CsDIiTDv9cSagXAVFjPdm2g4PFJqrNBeqdDIoYBnnta336HmamWHg==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-user-agent@3.620.0': @@ -1092,6 +1152,10 @@ packages: resolution: {integrity: sha512-djpnECwDLI/4sck1wxK/cZJmZX5pAhRvjONyJqr0AaOfJyuIAG0PHLe7xwCrv2rCAvIBR9ofnNFzPIGTJPDUwg==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.931.0': + resolution: {integrity: sha512-Ftd+f3+y5KNYKzLXaGknwJ9hCkFWshi5C9TLLsz+fEohWc1FvIKU7MlXTeFms2eN76TTVHuG8N2otaujl6CuHg==} + engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.840.0': resolution: {integrity: sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==} engines: {node: '>=18.0.0'} @@ -1100,6 +1164,10 @@ packages: resolution: {integrity: sha512-Jr/smgVrLZECQgMyP4nbGqgJwzFFbkjOVrU8wh/gbVIZy1+Gu6R7Shai7KHDkEjwkGcHpN1MCCO67jTAOoSlMw==} engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.931.0': + resolution: {integrity: sha512-6/dXrX2nWgiWdHxooEtmKpOErms4+79AQawEvhhxpLPpa+tixl4i/MSFgHk9sjkGv5a1/P3DbnedpZWl+2wMOg==} + engines: {node: '>=18.0.0'} + '@aws-sdk/region-config-resolver@3.614.0': resolution: {integrity: sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==} engines: {node: '>=16.0.0'} @@ -1112,8 +1180,12 @@ packages: resolution: {integrity: sha512-gzQAkuHI3xyG6toYnH/pju+kc190XmvnB7X84vtN57GjgdQJICt9So/BD0U6h+eSfk9VBnafkVrAzBzWMEFZVw==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.840.0': - resolution: {integrity: sha512-8AoVgHrkSfhvGPtwx23hIUO4MmMnux2pjnso1lrLZGqxfElM6jm2w4jTNLlNXk8uKHGyX89HaAIuT0lL6dJj9g==} + '@aws-sdk/region-config-resolver@3.930.0': + resolution: {integrity: sha512-KL2JZqH6aYeQssu1g1KuWsReupdfOoxD6f1as2VC+rdwYFUu4LfzMsFfXnBvvQWWqQ7rZHWOw1T+o5gJmg7Dzw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.931.0': + resolution: {integrity: sha512-EGYYDSSk7k1xbSHtb8MfEMILf5achdNnnsYKgFk0+Oul3tPQ4xUmOt5qRP6sOO3/LQHF37gBYHUF9OSA/+uVCw==} engines: {node: '>=18.0.0'} '@aws-sdk/token-providers@3.614.0': @@ -1130,6 +1202,10 @@ packages: resolution: {integrity: sha512-dQr3pFpzemKyrB7SEJ2ipPtWrZiL5vaimg2PkXpwyzGrigYRc8F2R9DMUckU5zi32ozvQqq4PI3bOrw6xUfcbQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/token-providers@3.931.0': + resolution: {integrity: sha512-dr+02X9oxqmXG0856odFJ7wAXy12pr/tq2Zg+IS0TDThFvgtvx4yChkpqmc89wGoW+Aly47JPfPUXh0IMpGzIg==} + engines: {node: '>=18.0.0'} + '@aws-sdk/types@3.609.0': resolution: {integrity: sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==} engines: {node: '>=16.0.0'} @@ -1142,8 +1218,12 @@ packages: resolution: {integrity: sha512-o67gL3vjf4nhfmuSUNNkit0d62QJEwwHLxucwVJkR/rw9mfUtAWsgBs8Tp16cdUbMgsyQtCQilL8RAJDoGtadQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-arn-parser@3.804.0': - resolution: {integrity: sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==} + '@aws-sdk/types@3.930.0': + resolution: {integrity: sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} '@aws-sdk/util-dynamodb@3.840.0': @@ -1164,6 +1244,10 @@ packages: resolution: {integrity: sha512-6XgdNe42ibP8zCQgNGDWoOF53RfEKzpU/S7Z29FTTJ7hcZv0SytC0ZNQQZSx4rfBl036YWYwJRoJMlT4AA7q9A==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-endpoints@3.930.0': + resolution: {integrity: sha512-M2oEKBzzNAYr136RRc6uqw3aWlwCxqTP1Lawps9E1d2abRPvl1p1ztQmmXp1Ak4rv8eByIZ+yQyKQ3zPdRG5dw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/util-locate-window@3.465.0': resolution: {integrity: sha512-f+QNcWGswredzC1ExNAB/QzODlxwaTdXkNT5cvke2RLX8SFU5pYk6h4uCtWC0vWPELzOfMfloBrJefBzlarhsw==} engines: {node: '>=14.0.0'} @@ -1177,6 +1261,9 @@ packages: '@aws-sdk/util-user-agent-browser@3.910.0': resolution: {integrity: sha512-iOdrRdLZHrlINk9pezNZ82P/VxO/UmtmpaOAObUN+xplCUJu31WNM2EE/HccC8PQw6XlAudpdA6HDTGiW6yVGg==} + '@aws-sdk/util-user-agent-browser@3.930.0': + resolution: {integrity: sha512-q6lCRm6UAe+e1LguM5E4EqM9brQlDem4XDcQ87NzEvlTW6GzmNCO0w1jS0XgCFXQHjDxjdlNFX+5sRbHijwklg==} + '@aws-sdk/util-user-agent-node@3.614.0': resolution: {integrity: sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==} engines: {node: '>=16.0.0'} @@ -1204,6 +1291,15 @@ packages: aws-crt: optional: true + '@aws-sdk/util-user-agent-node@3.931.0': + resolution: {integrity: sha512-j5if01rt7JCGYDVXck39V7IUyKAN73vKUPzmu+jp1apU3Q0lLSTZA/HCfL2HkMUKVLE67ibjKb+NCoEg0QhujA==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + '@aws-sdk/xml-builder@3.821.0': resolution: {integrity: sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==} engines: {node: '>=18.0.0'} @@ -1212,10 +1308,18 @@ packages: resolution: {integrity: sha512-UK0NzRknzUITYlkDibDSgkWvhhC11OLhhhGajl6pYCACup+6QE4SsLvmAGMkyNtGVCJ6Q+BM6PwDCBZyBgwl9A==} engines: {node: '>=18.0.0'} + '@aws-sdk/xml-builder@3.930.0': + resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} + engines: {node: '>=18.0.0'} + '@aws/lambda-invoke-store@0.0.1': resolution: {integrity: sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==} engines: {node: '>=18.0.0'} + '@aws/lambda-invoke-store@0.1.1': + resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -2790,113 +2894,113 @@ packages: '@polka/url@1.0.0-next.24': resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} - '@rollup/rollup-android-arm-eabi@4.52.4': - resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.4': - resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.4': - resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.4': - resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.4': - resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.4': - resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': - resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.4': - resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.4': - resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.4': - resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.4': - resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.4': - resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.4': - resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.4': - resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.4': - resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.4': - resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.4': - resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.4': - resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.4': - resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.4': - resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.4': - resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.4': - resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} cpu: [x64] os: [win32] @@ -2947,30 +3051,22 @@ packages: resolution: {integrity: sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==} engines: {node: '>=16.0.0'} - '@smithy/abort-controller@4.2.3': - resolution: {integrity: sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==} - engines: {node: '>=18.0.0'} - '@smithy/abort-controller@4.2.5': resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader-native@4.0.0': - resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader@5.0.0': - resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} engines: {node: '>=18.0.0'} '@smithy/config-resolver@3.0.5': resolution: {integrity: sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==} engines: {node: '>=16.0.0'} - '@smithy/config-resolver@4.3.3': - resolution: {integrity: sha512-xSql8A1Bl41O9JvGU/CtgiLBlwkvpHTSKRlvz9zOBvBCPjXghZ6ZkcVzmV2f7FLAA+80+aqKmIOmy8pEDrtCaw==} - engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.4.3': resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} engines: {node: '>=18.0.0'} @@ -2979,12 +3075,8 @@ packages: resolution: {integrity: sha512-BC7VMXx/1BCmRPCVzzn4HGWAtsrb7/0758EtwOGFJQrlSwJBEjCcDLNZLFoL/68JexYa2s+KmgL/UfmXdG6v1w==} engines: {node: '>=16.0.0'} - '@smithy/core@3.17.0': - resolution: {integrity: sha512-Tir3DbfoTO97fEGUZjzGeoXgcQAUBRDTmuH9A8lxuP8ATrgezrAJ6cLuRvwdKN4ZbYNlHgKlBX69Hyu3THYhtg==} - engines: {node: '>=18.0.0'} - - '@smithy/core@3.18.0': - resolution: {integrity: sha512-vGSDXOJFZgOPTatSI1ly7Gwyy/d/R9zh2TO3y0JZ0uut5qQ88p9IaWaZYIWSSqtdekNM4CGok/JppxbAff4KcQ==} + '@smithy/core@3.18.3': + resolution: {integrity: sha512-qqpNskkbHOSfrbFbjhYj5o8VMXO26fvN1K/+HbCzUNlTuxgNcPRouUDNm+7D6CkN244WG7aK533Ne18UtJEgAA==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@3.2.0': @@ -2999,64 +3091,52 @@ packages: resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@4.0.4': - resolution: {integrity: sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==} + '@smithy/eventstream-codec@4.2.5': + resolution: {integrity: sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-browser@4.0.4': - resolution: {integrity: sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==} + '@smithy/eventstream-serde-browser@4.2.5': + resolution: {integrity: sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-config-resolver@4.1.2': - resolution: {integrity: sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==} + '@smithy/eventstream-serde-config-resolver@4.3.5': + resolution: {integrity: sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-node@4.0.4': - resolution: {integrity: sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==} + '@smithy/eventstream-serde-node@4.2.5': + resolution: {integrity: sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-universal@4.0.4': - resolution: {integrity: sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==} + '@smithy/eventstream-serde-universal@4.2.5': + resolution: {integrity: sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==} engines: {node: '>=18.0.0'} '@smithy/fetch-http-handler@3.2.4': resolution: {integrity: sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==} - '@smithy/fetch-http-handler@5.3.4': - resolution: {integrity: sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==} - engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.3.6': resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} engines: {node: '>=18.0.0'} - '@smithy/hash-blob-browser@4.0.4': - resolution: {integrity: sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==} + '@smithy/hash-blob-browser@4.2.6': + resolution: {integrity: sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==} engines: {node: '>=18.0.0'} '@smithy/hash-node@3.0.3': resolution: {integrity: sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==} engines: {node: '>=16.0.0'} - '@smithy/hash-node@4.2.3': - resolution: {integrity: sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==} - engines: {node: '>=18.0.0'} - '@smithy/hash-node@4.2.5': resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} engines: {node: '>=18.0.0'} - '@smithy/hash-stream-node@4.0.4': - resolution: {integrity: sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==} + '@smithy/hash-stream-node@4.2.5': + resolution: {integrity: sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==} engines: {node: '>=18.0.0'} '@smithy/invalid-dependency@3.0.3': resolution: {integrity: sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==} - '@smithy/invalid-dependency@4.2.3': - resolution: {integrity: sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==} - engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@4.2.5': resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} engines: {node: '>=18.0.0'} @@ -3073,8 +3153,8 @@ packages: resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@4.0.4': - resolution: {integrity: sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==} + '@smithy/md5-js@4.2.5': + resolution: {integrity: sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==} engines: {node: '>=18.0.0'} '@smithy/middleware-compression@3.0.7': @@ -3089,10 +3169,6 @@ packages: resolution: {integrity: sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==} engines: {node: '>=16.0.0'} - '@smithy/middleware-content-length@4.2.3': - resolution: {integrity: sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.2.5': resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} engines: {node: '>=18.0.0'} @@ -3101,34 +3177,22 @@ packages: resolution: {integrity: sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==} engines: {node: '>=16.0.0'} - '@smithy/middleware-endpoint@4.3.4': - resolution: {integrity: sha512-/RJhpYkMOaUZoJEkddamGPPIYeKICKXOu/ojhn85dKDM0n5iDIhjvYAQLP3K5FPhgB203O3GpWzoK2OehEoIUw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-endpoint@4.3.7': - resolution: {integrity: sha512-i8Mi8OuY6Yi82Foe3iu7/yhBj1HBRoOQwBSsUNYglJTNSFaWYTNM2NauBBs/7pq2sqkLRqeUXA3Ogi2utzpUlQ==} + '@smithy/middleware-endpoint@4.3.10': + resolution: {integrity: sha512-SoAag3QnWBFoXjwa1jenEThkzJYClidZUyqsLKwWZ8kOlZBwehrLBp4ygVDjNEM2a2AamCQ2FBA/HuzKJ/LiTA==} engines: {node: '>=18.0.0'} '@smithy/middleware-retry@3.0.13': resolution: {integrity: sha512-zvCLfaRYCaUmjbF2yxShGZdolSHft7NNCTA28HVN9hKcEbOH+g5irr1X9s+in8EpambclGnevZY4A3lYpvDCFw==} engines: {node: '>=16.0.0'} - '@smithy/middleware-retry@4.4.4': - resolution: {integrity: sha512-vSgABQAkuUHRO03AhR2rWxVQ1un284lkBn+NFawzdahmzksAoOeVMnXXsuPViL4GlhRHXqFaMlc8Mj04OfQk1w==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-retry@4.4.7': - resolution: {integrity: sha512-E7Vc6WHCHlzDRTx1W0jZ6J1L6ziEV0PIWcUdmfL4y+c8r7WYr6I+LkQudaD8Nfb7C5c4P3SQ972OmXHtv6m/OA==} + '@smithy/middleware-retry@4.4.10': + resolution: {integrity: sha512-6fOwX34gXxcqKa3bsG0mR0arc2Cw4ddOS6tp3RgUD2yoTrDTbQ2aVADnDjhUuxaiDZN2iilxndgGDhnpL/XvJA==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@3.0.3': resolution: {integrity: sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==} engines: {node: '>=16.0.0'} - '@smithy/middleware-serde@4.2.3': - resolution: {integrity: sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.2.5': resolution: {integrity: sha512-La1ldWTJTZ5NqQyPqnCNeH9B+zjFhrNoQIL1jTh4zuqXRlmXhxYHhMtI1/92OlnoAtp6JoN7kzuwhWoXrBwPqg==} engines: {node: '>=18.0.0'} @@ -3137,10 +3201,6 @@ packages: resolution: {integrity: sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==} engines: {node: '>=16.0.0'} - '@smithy/middleware-stack@4.2.3': - resolution: {integrity: sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.2.5': resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} engines: {node: '>=18.0.0'} @@ -3149,10 +3209,6 @@ packages: resolution: {integrity: sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==} engines: {node: '>=16.0.0'} - '@smithy/node-config-provider@4.3.3': - resolution: {integrity: sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==} - engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.3.5': resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} engines: {node: '>=18.0.0'} @@ -3161,10 +3217,6 @@ packages: resolution: {integrity: sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==} engines: {node: '>=16.0.0'} - '@smithy/node-http-handler@4.4.2': - resolution: {integrity: sha512-MHFvTjts24cjGo1byXqhXrbqm7uznFD/ESFx8npHMWTFQVdBZjrT1hKottmp69LBTRm/JQzP/sn1vPt0/r6AYQ==} - engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.4.5': resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} engines: {node: '>=18.0.0'} @@ -3189,10 +3241,6 @@ packages: resolution: {integrity: sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==} engines: {node: '>=16.0.0'} - '@smithy/protocol-http@5.3.3': - resolution: {integrity: sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==} - engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.3.5': resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} engines: {node: '>=18.0.0'} @@ -3201,10 +3249,6 @@ packages: resolution: {integrity: sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==} engines: {node: '>=16.0.0'} - '@smithy/querystring-builder@4.2.3': - resolution: {integrity: sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==} - engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@4.2.5': resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} engines: {node: '>=18.0.0'} @@ -3213,10 +3257,6 @@ packages: resolution: {integrity: sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==} engines: {node: '>=16.0.0'} - '@smithy/querystring-parser@4.2.3': - resolution: {integrity: sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==} - engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@4.2.5': resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} engines: {node: '>=18.0.0'} @@ -3225,10 +3265,6 @@ packages: resolution: {integrity: sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==} engines: {node: '>=16.0.0'} - '@smithy/service-error-classification@4.2.3': - resolution: {integrity: sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==} - engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.2.5': resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} engines: {node: '>=18.0.0'} @@ -3237,10 +3273,6 @@ packages: resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==} engines: {node: '>=16.0.0'} - '@smithy/shared-ini-file-loader@4.3.3': - resolution: {integrity: sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==} - engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.4.0': resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} engines: {node: '>=18.0.0'} @@ -3249,10 +3281,6 @@ packages: resolution: {integrity: sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==} engines: {node: '>=16.0.0'} - '@smithy/signature-v4@5.3.3': - resolution: {integrity: sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==} - engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.3.5': resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} engines: {node: '>=18.0.0'} @@ -3261,12 +3289,8 @@ packages: resolution: {integrity: sha512-l0BpyYkciNyMaS+PnFFz4aO5sBcXvGLoJd7mX9xrMBIm2nIQBVvYgp2ZpPDMzwjKCavsXu06iuCm0F6ZJZc6yQ==} engines: {node: '>=16.0.0'} - '@smithy/smithy-client@4.9.0': - resolution: {integrity: sha512-qz7RTd15GGdwJ3ZCeBKLDQuUQ88m+skh2hJwcpPm1VqLeKzgZvXf6SrNbxvx7uOqvvkjCMXqx3YB5PDJyk00ww==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.9.3': - resolution: {integrity: sha512-8tlueuTgV5n7inQCkhyptrB3jo2AO80uGrps/XTYZivv5MFQKKBj3CIWIGMI2fRY5LEduIiazOhAWdFknY1O9w==} + '@smithy/smithy-client@4.9.6': + resolution: {integrity: sha512-hGz42hggqReicRRZUvrKDQiAmoJnx1Q+XfAJnYAGu544gOfxQCAC3hGGD7+Px2gEUUxB/kKtQV7LOtBRNyxteQ==} engines: {node: '>=18.0.0'} '@smithy/types@2.12.0': @@ -3277,10 +3301,6 @@ packages: resolution: {integrity: sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==} engines: {node: '>=16.0.0'} - '@smithy/types@4.8.0': - resolution: {integrity: sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==} - engines: {node: '>=18.0.0'} - '@smithy/types@4.9.0': resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} engines: {node: '>=18.0.0'} @@ -3288,10 +3308,6 @@ packages: '@smithy/url-parser@3.0.3': resolution: {integrity: sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==} - '@smithy/url-parser@4.2.3': - resolution: {integrity: sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==} - engines: {node: '>=18.0.0'} - '@smithy/url-parser@4.2.5': resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} engines: {node: '>=18.0.0'} @@ -3343,34 +3359,22 @@ packages: resolution: {integrity: sha512-ZIRSUsnnMRStOP6OKtW+gCSiVFkwnfQF2xtf32QKAbHR6ACjhbAybDvry+3L5qQYdh3H6+7yD/AiUE45n8mTTw==} engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-browser@4.3.3': - resolution: {integrity: sha512-vqHoybAuZXbFXZqgzquiUXtdY+UT/aU33sxa4GBPkiYklmR20LlCn+d3Wc3yA5ZM13gQ92SZe/D8xh6hkjx+IQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.3.6': - resolution: {integrity: sha512-kbpuXbEf2YQ9zEE6eeVnUCQWO0e1BjMnKrXL8rfXgiWA0m8/E0leU4oSNzxP04WfCmW8vjEqaDeXWxwE4tpOjQ==} + '@smithy/util-defaults-mode-browser@4.3.9': + resolution: {integrity: sha512-Bh5bU40BgdkXE2BcaNazhNtEXi1TC0S+1d84vUwv5srWfvbeRNUKFzwKQgC6p6MXPvEgw+9+HdX3pOwT6ut5aw==} engines: {node: '>=18.0.0'} '@smithy/util-defaults-mode-node@3.0.13': resolution: {integrity: sha512-voUa8TFJGfD+U12tlNNLCDlXibt9vRdNzRX45Onk/WxZe7TS+hTOZouEZRa7oARGicdgeXvt1A0W45qLGYdy+g==} engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-node@4.2.4': - resolution: {integrity: sha512-X5/xrPHedifo7hJUUWKlpxVb2oDOiqPUXlvsZv1EZSjILoutLiJyWva3coBpn00e/gPSpH8Rn2eIbgdwHQdW7Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.2.9': - resolution: {integrity: sha512-dgyribrVWN5qE5usYJ0m5M93mVM3L3TyBPZWe1Xl6uZlH2gzfQx3dz+ZCdW93lWqdedJRkOecnvbnoEEXRZ5VQ==} + '@smithy/util-defaults-mode-node@4.2.12': + resolution: {integrity: sha512-EHZwe1E9Q7umImIyCKQg/Cm+S+7rjXxCRvfGmKifqwYvn7M8M4ZcowwUOQzvuuxUUmdzCkqL0Eq0z1m74Pq6pw==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@2.0.5': resolution: {integrity: sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==} engines: {node: '>=16.0.0'} - '@smithy/util-endpoints@3.2.3': - resolution: {integrity: sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==} - engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@3.2.5': resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} engines: {node: '>=18.0.0'} @@ -3387,10 +3391,6 @@ packages: resolution: {integrity: sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==} engines: {node: '>=16.0.0'} - '@smithy/util-middleware@4.2.3': - resolution: {integrity: sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==} - engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.2.5': resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} engines: {node: '>=18.0.0'} @@ -3399,10 +3399,6 @@ packages: resolution: {integrity: sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==} engines: {node: '>=16.0.0'} - '@smithy/util-retry@4.2.3': - resolution: {integrity: sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==} - engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.2.5': resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} engines: {node: '>=18.0.0'} @@ -3411,10 +3407,6 @@ packages: resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==} engines: {node: '>=16.0.0'} - '@smithy/util-stream@4.5.3': - resolution: {integrity: sha512-oZvn8a5bwwQBNYHT2eNo0EU8Kkby3jeIg1P2Lu9EQtqDxki1LIjGRJM6dJ5CZUig8QmLxWxqOKWvg3mVoOBs5A==} - engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.5.6': resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} engines: {node: '>=18.0.0'} @@ -3443,8 +3435,8 @@ packages: resolution: {integrity: sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==} engines: {node: '>=16.0.0'} - '@smithy/util-waiter@4.0.6': - resolution: {integrity: sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==} + '@smithy/util-waiter@4.2.5': + resolution: {integrity: sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==} engines: {node: '>=18.0.0'} '@smithy/uuid@1.1.0': @@ -8386,6 +8378,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.13.0: @@ -8654,8 +8647,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.52.4: - resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10139,20 +10132,20 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.910.0 + '@aws-sdk/types': 3.930.0 tslib: 2.6.2 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.910.0 + '@aws-sdk/types': 3.930.0 tslib: 2.6.2 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.910.0 + '@aws-sdk/types': 3.930.0 '@aws-sdk/util-locate-window': 3.465.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 @@ -10162,7 +10155,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.609.0 + '@aws-sdk/types': 3.930.0 '@aws-sdk/util-locate-window': 3.465.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 @@ -10170,7 +10163,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.609.0 + '@aws-sdk/types': 3.930.0 tslib: 2.6.2 '@aws-crypto/supports-web-crypto@5.2.0': @@ -10179,7 +10172,7 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.609.0 + '@aws-sdk/types': 3.930.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 @@ -10246,33 +10239,33 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-compression': 4.1.12 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.0.6 + '@smithy/util-waiter': 4.2.5 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -10338,30 +10331,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 transitivePeerDependencies: @@ -10383,98 +10376,95 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.0.6 + '@smithy/util-waiter': 4.2.5 '@types/uuid': 9.0.8 tslib: 2.6.2 uuid: 9.0.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-s3@3.842.0': + '@aws-sdk/client-s3@3.931.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.840.0 - '@aws-sdk/credential-provider-node': 3.840.0 - '@aws-sdk/middleware-bucket-endpoint': 3.840.0 - '@aws-sdk/middleware-expect-continue': 3.840.0 - '@aws-sdk/middleware-flexible-checksums': 3.840.0 - '@aws-sdk/middleware-host-header': 3.840.0 - '@aws-sdk/middleware-location-constraint': 3.840.0 - '@aws-sdk/middleware-logger': 3.840.0 - '@aws-sdk/middleware-recursion-detection': 3.840.0 - '@aws-sdk/middleware-sdk-s3': 3.840.0 - '@aws-sdk/middleware-ssec': 3.840.0 - '@aws-sdk/middleware-user-agent': 3.840.0 - '@aws-sdk/region-config-resolver': 3.840.0 - '@aws-sdk/signature-v4-multi-region': 3.840.0 - '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-endpoints': 3.840.0 - '@aws-sdk/util-user-agent-browser': 3.840.0 - '@aws-sdk/util-user-agent-node': 3.840.0 - '@aws-sdk/xml-builder': 3.821.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/eventstream-serde-browser': 4.0.4 - '@smithy/eventstream-serde-config-resolver': 4.1.2 - '@smithy/eventstream-serde-node': 4.0.4 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-blob-browser': 4.0.4 - '@smithy/hash-node': 4.2.3 - '@smithy/hash-stream-node': 4.0.4 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/md5-js': 4.0.4 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@aws-sdk/core': 3.931.0 + '@aws-sdk/credential-provider-node': 3.931.0 + '@aws-sdk/middleware-bucket-endpoint': 3.930.0 + '@aws-sdk/middleware-expect-continue': 3.930.0 + '@aws-sdk/middleware-flexible-checksums': 3.931.0 + '@aws-sdk/middleware-host-header': 3.930.0 + '@aws-sdk/middleware-location-constraint': 3.930.0 + '@aws-sdk/middleware-logger': 3.930.0 + '@aws-sdk/middleware-recursion-detection': 3.930.0 + '@aws-sdk/middleware-sdk-s3': 3.931.0 + '@aws-sdk/middleware-ssec': 3.930.0 + '@aws-sdk/middleware-user-agent': 3.931.0 + '@aws-sdk/region-config-resolver': 3.930.0 + '@aws-sdk/signature-v4-multi-region': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-endpoints': 3.930.0 + '@aws-sdk/util-user-agent-browser': 3.930.0 + '@aws-sdk/util-user-agent-node': 3.931.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/eventstream-serde-browser': 4.2.5 + '@smithy/eventstream-serde-config-resolver': 4.3.5 + '@smithy/eventstream-serde-node': 4.2.5 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-blob-browser': 4.2.6 + '@smithy/hash-node': 4.2.5 + '@smithy/hash-stream-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/md5-js': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/util-stream': 4.5.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-stream': 4.5.6 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.0.6 - '@types/uuid': 9.0.8 + '@smithy/util-waiter': 4.2.5 tslib: 2.6.2 - uuid: 9.0.1 transitivePeerDependencies: - aws-crt @@ -10541,32 +10531,32 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.0.6 + '@smithy/util-waiter': 4.2.5 '@types/uuid': 9.0.8 tslib: 2.6.2 uuid: 9.0.1 @@ -10634,26 +10624,26 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.910.0 '@aws-sdk/util-user-agent-node': 3.910.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 '@smithy/fetch-http-handler': 5.3.6 '@smithy/hash-node': 4.2.5 '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.7 - '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 '@smithy/middleware-serde': 4.2.5 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.6 - '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -10719,30 +10709,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 transitivePeerDependencies: @@ -10763,26 +10753,69 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.910.0 '@aws-sdk/util-user-agent-node': 3.910.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.931.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.931.0 + '@aws-sdk/middleware-host-header': 3.930.0 + '@aws-sdk/middleware-logger': 3.930.0 + '@aws-sdk/middleware-recursion-detection': 3.930.0 + '@aws-sdk/middleware-user-agent': 3.931.0 + '@aws-sdk/region-config-resolver': 3.930.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-endpoints': 3.930.0 + '@aws-sdk/util-user-agent-browser': 3.930.0 + '@aws-sdk/util-user-agent-node': 3.931.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 '@smithy/fetch-http-handler': 5.3.6 '@smithy/hash-node': 4.2.5 '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.7 - '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 '@smithy/middleware-serde': 4.2.5 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.6 - '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -10852,16 +10885,16 @@ snapshots: dependencies: '@aws-sdk/types': 3.840.0 '@aws-sdk/xml-builder': 3.821.0 - '@smithy/core': 3.17.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 + '@smithy/core': 3.18.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.3 + '@smithy/util-middleware': 4.2.5 '@smithy/util-utf8': 4.2.0 fast-xml-parser: 4.4.1 tslib: 2.6.2 @@ -10870,19 +10903,35 @@ snapshots: dependencies: '@aws-sdk/types': 3.910.0 '@aws-sdk/xml-builder': 3.910.0 - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 '@smithy/node-config-provider': 4.3.5 '@smithy/property-provider': 4.2.5 '@smithy/protocol-http': 5.3.5 '@smithy/signature-v4': 5.3.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/util-base64': 4.3.0 '@smithy/util-middleware': 4.2.5 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 - '@aws-sdk/credential-provider-cognito-identity@3.621.0': + '@aws-sdk/core@3.931.0': + dependencies: + '@aws-sdk/types': 3.930.0 + '@aws-sdk/xml-builder': 3.930.0 + '@smithy/core': 3.18.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-cognito-identity@3.621.0': dependencies: '@aws-sdk/client-cognito-identity': 3.621.0 '@aws-sdk/types': 3.609.0 @@ -10896,8 +10945,8 @@ snapshots: dependencies: '@aws-sdk/client-cognito-identity': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -10913,8 +10962,8 @@ snapshots: dependencies: '@aws-sdk/core': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/credential-provider-env@3.910.0': @@ -10925,6 +10974,14 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 + '@aws-sdk/credential-provider-env@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + '@aws-sdk/credential-provider-http@3.621.0': dependencies: '@aws-sdk/types': 3.609.0 @@ -10941,13 +10998,13 @@ snapshots: dependencies: '@aws-sdk/core': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.2 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 tslib: 2.6.2 '@aws-sdk/credential-provider-http@3.910.0': @@ -10958,7 +11015,20 @@ snapshots: '@smithy/node-http-handler': 4.4.5 '@smithy/property-provider': 4.2.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-http@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/util-stream': 4.5.6 tslib: 2.6.2 @@ -11009,10 +11079,10 @@ snapshots: '@aws-sdk/credential-provider-web-identity': 3.840.0 '@aws-sdk/nested-clients': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11035,6 +11105,24 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/credential-provider-env': 3.931.0 + '@aws-sdk/credential-provider-http': 3.931.0 + '@aws-sdk/credential-provider-process': 3.931.0 + '@aws-sdk/credential-provider-sso': 3.931.0 + '@aws-sdk/credential-provider-web-identity': 3.931.0 + '@aws-sdk/nested-clients': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-node@3.621.0(@aws-sdk/client-sso-oidc@3.621.0(@aws-sdk/client-sts@3.621.0))(@aws-sdk/client-sts@3.621.0)': dependencies: '@aws-sdk/credential-provider-env': 3.620.1 @@ -11082,10 +11170,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.840.0 '@aws-sdk/credential-provider-web-identity': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11107,6 +11195,23 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.931.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.931.0 + '@aws-sdk/credential-provider-http': 3.931.0 + '@aws-sdk/credential-provider-ini': 3.931.0 + '@aws-sdk/credential-provider-process': 3.931.0 + '@aws-sdk/credential-provider-sso': 3.931.0 + '@aws-sdk/credential-provider-web-identity': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-process@3.620.1': dependencies: '@aws-sdk/types': 3.609.0 @@ -11119,9 +11224,9 @@ snapshots: dependencies: '@aws-sdk/core': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/credential-provider-process@3.910.0': @@ -11133,6 +11238,15 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 + '@aws-sdk/credential-provider-process@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + '@aws-sdk/credential-provider-sso@3.621.0(@aws-sdk/client-sso-oidc@3.621.0(@aws-sdk/client-sts@3.621.0))': dependencies: '@aws-sdk/client-sso': 3.621.0 @@ -11165,9 +11279,9 @@ snapshots: '@aws-sdk/core': 3.840.0 '@aws-sdk/token-providers': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11185,6 +11299,19 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-sso@3.931.0': + dependencies: + '@aws-sdk/client-sso': 3.931.0 + '@aws-sdk/core': 3.931.0 + '@aws-sdk/token-providers': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.621.0)': dependencies: '@aws-sdk/client-sts': 3.621.0 @@ -11198,8 +11325,8 @@ snapshots: '@aws-sdk/core': 3.840.0 '@aws-sdk/nested-clients': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11216,6 +11343,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/nested-clients': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-providers@3.621.0(@aws-sdk/client-sso-oidc@3.910.0)': dependencies: '@aws-sdk/client-cognito-identity': 3.621.0 @@ -11252,12 +11391,12 @@ snapshots: '@aws-sdk/credential-provider-web-identity': 3.840.0 '@aws-sdk/nested-clients': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 '@smithy/credential-provider-imds': 4.2.3 - '@smithy/node-config-provider': 4.3.3 + '@smithy/node-config-provider': 4.3.5 '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11272,18 +11411,18 @@ snapshots: '@aws-sdk/client-dynamodb': 3.840.0 '@aws-sdk/core': 3.840.0 '@aws-sdk/util-dynamodb': 3.840.0(@aws-sdk/client-dynamodb@3.840.0) - '@smithy/core': 3.17.0 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 + '@smithy/core': 3.18.3 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/middleware-bucket-endpoint@3.840.0': + '@aws-sdk/middleware-bucket-endpoint@3.930.0': dependencies: - '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-arn-parser': 3.804.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 '@smithy/util-config-provider': 4.2.0 tslib: 2.6.2 @@ -11291,31 +11430,31 @@ snapshots: dependencies: '@aws-sdk/endpoint-cache': 3.804.0 '@aws-sdk/types': 3.840.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/middleware-expect-continue@3.840.0': + '@aws-sdk/middleware-expect-continue@3.930.0': dependencies: - '@aws-sdk/types': 3.840.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/middleware-flexible-checksums@3.840.0': + '@aws-sdk/middleware-flexible-checksums@3.931.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.840.0 - '@aws-sdk/types': 3.840.0 + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 '@smithy/is-array-buffer': 4.2.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 @@ -11329,8 +11468,8 @@ snapshots: '@aws-sdk/middleware-host-header@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-host-header@3.910.0': @@ -11340,10 +11479,17 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/middleware-location-constraint@3.840.0': + '@aws-sdk/middleware-host-header@3.930.0': dependencies: - '@aws-sdk/types': 3.840.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-location-constraint@3.930.0': + dependencies: + '@aws-sdk/types': 3.930.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-logger@3.609.0': @@ -11355,7 +11501,7 @@ snapshots: '@aws-sdk/middleware-logger@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-logger@3.910.0': @@ -11364,6 +11510,12 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 + '@aws-sdk/middleware-logger@3.930.0': + dependencies: + '@aws-sdk/types': 3.930.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + '@aws-sdk/middleware-recursion-detection@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 @@ -11374,8 +11526,8 @@ snapshots: '@aws-sdk/middleware-recursion-detection@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-recursion-detection@3.910.0': @@ -11386,27 +11538,35 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/middleware-sdk-s3@3.840.0': + '@aws-sdk/middleware-recursion-detection@3.930.0': dependencies: - '@aws-sdk/core': 3.840.0 - '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-arn-parser': 3.804.0 - '@smithy/core': 3.17.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@aws/lambda-invoke-store': 0.1.1 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-s3@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.18.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.3 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 - '@aws-sdk/middleware-ssec@3.840.0': + '@aws-sdk/middleware-ssec@3.930.0': dependencies: - '@aws-sdk/types': 3.840.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-user-agent@3.620.0': @@ -11422,9 +11582,9 @@ snapshots: '@aws-sdk/core': 3.840.0 '@aws-sdk/types': 3.840.0 '@aws-sdk/util-endpoints': 3.840.0 - '@smithy/core': 3.17.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/core': 3.18.3 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/middleware-user-agent@3.910.0': @@ -11432,7 +11592,17 @@ snapshots: '@aws-sdk/core': 3.910.0 '@aws-sdk/types': 3.910.0 '@aws-sdk/util-endpoints': 3.910.0 - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-user-agent@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-endpoints': 3.930.0 + '@smithy/core': 3.18.3 '@smithy/protocol-http': 5.3.5 '@smithy/types': 4.9.0 tslib: 2.6.2 @@ -11451,30 +11621,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.840.0 '@aws-sdk/util-user-agent-browser': 3.840.0 '@aws-sdk/util-user-agent-node': 3.840.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 transitivePeerDependencies: @@ -11495,26 +11665,69 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.910.0 '@aws-sdk/util-user-agent-node': 3.910.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 '@smithy/fetch-http-handler': 5.3.6 '@smithy/hash-node': 4.2.5 '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.7 - '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 '@smithy/middleware-serde': 4.2.5 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.6 - '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/nested-clients@3.931.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.931.0 + '@aws-sdk/middleware-host-header': 3.930.0 + '@aws-sdk/middleware-logger': 3.930.0 + '@aws-sdk/middleware-recursion-detection': 3.930.0 + '@aws-sdk/middleware-user-agent': 3.931.0 + '@aws-sdk/region-config-resolver': 3.930.0 + '@aws-sdk/types': 3.930.0 + '@aws-sdk/util-endpoints': 3.930.0 + '@aws-sdk/util-user-agent-browser': 3.930.0 + '@aws-sdk/util-user-agent-node': 3.931.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.3 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.10 + '@smithy/middleware-retry': 4.4.10 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.6 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.9 + '@smithy/util-defaults-mode-node': 4.2.12 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -11535,10 +11748,10 @@ snapshots: '@aws-sdk/region-config-resolver@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 + '@smithy/util-middleware': 4.2.5 tslib: 2.6.2 '@aws-sdk/region-config-resolver@3.910.0': @@ -11550,13 +11763,21 @@ snapshots: '@smithy/util-middleware': 4.2.5 tslib: 2.6.2 - '@aws-sdk/signature-v4-multi-region@3.840.0': + '@aws-sdk/region-config-resolver@3.930.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.840.0 - '@aws-sdk/types': 3.840.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.930.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + + '@aws-sdk/signature-v4-multi-region@3.931.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.621.0(@aws-sdk/client-sts@3.621.0))': @@ -11582,9 +11803,9 @@ snapshots: '@aws-sdk/core': 3.840.0 '@aws-sdk/nested-clients': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 transitivePeerDependencies: - aws-crt @@ -11601,6 +11822,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/token-providers@3.931.0': + dependencies: + '@aws-sdk/core': 3.931.0 + '@aws-sdk/nested-clients': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/types@3.609.0': dependencies: '@smithy/types': 3.3.0 @@ -11608,15 +11841,20 @@ snapshots: '@aws-sdk/types@3.840.0': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/types@3.910.0': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@aws-sdk/util-arn-parser@3.804.0': + '@aws-sdk/types@3.930.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.6.2 + + '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.6.2 @@ -11635,8 +11873,8 @@ snapshots: '@aws-sdk/util-endpoints@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/types': 4.8.0 - '@smithy/util-endpoints': 3.2.3 + '@smithy/types': 4.9.0 + '@smithy/util-endpoints': 3.2.5 tslib: 2.6.2 '@aws-sdk/util-endpoints@3.910.0': @@ -11647,6 +11885,14 @@ snapshots: '@smithy/util-endpoints': 3.2.5 tslib: 2.6.2 + '@aws-sdk/util-endpoints@3.930.0': + dependencies: + '@aws-sdk/types': 3.930.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-endpoints': 3.2.5 + tslib: 2.6.2 + '@aws-sdk/util-locate-window@3.465.0': dependencies: tslib: 2.6.2 @@ -11661,7 +11907,7 @@ snapshots: '@aws-sdk/util-user-agent-browser@3.840.0': dependencies: '@aws-sdk/types': 3.840.0 - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 bowser: 2.12.1 tslib: 2.6.2 @@ -11672,6 +11918,13 @@ snapshots: bowser: 2.12.1 tslib: 2.6.2 + '@aws-sdk/util-user-agent-browser@3.930.0': + dependencies: + '@aws-sdk/types': 3.930.0 + '@smithy/types': 4.9.0 + bowser: 2.12.1 + tslib: 2.6.2 + '@aws-sdk/util-user-agent-node@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 @@ -11683,8 +11936,8 @@ snapshots: dependencies: '@aws-sdk/middleware-user-agent': 3.840.0 '@aws-sdk/types': 3.840.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/util-user-agent-node@3.910.0': @@ -11695,9 +11948,17 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.6.2 + '@aws-sdk/util-user-agent-node@3.931.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.931.0 + '@aws-sdk/types': 3.930.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.6.2 + '@aws-sdk/xml-builder@3.821.0': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@aws-sdk/xml-builder@3.910.0': @@ -11706,8 +11967,16 @@ snapshots: fast-xml-parser: 5.2.5 tslib: 2.6.2 + '@aws-sdk/xml-builder@3.930.0': + dependencies: + '@smithy/types': 4.9.0 + fast-xml-parser: 5.2.5 + tslib: 2.6.2 + '@aws/lambda-invoke-store@0.0.1': {} + '@aws/lambda-invoke-store@0.1.1': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -12975,13 +13244,13 @@ snapshots: '@guardian/eslint-config-typescript@12.0.0(eslint@8.57.1)(tslib@2.6.2)(typescript@5.5.3)': dependencies: - '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2) + '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2) '@stylistic/eslint-plugin': 2.6.2(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) tslib: 2.6.2 typescript: 5.5.3 transitivePeerDependencies: @@ -13010,12 +13279,12 @@ snapshots: - supports-color - typescript - '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2)': + '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2)': dependencies: eslint: 8.57.1 eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) tslib: 2.6.2 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -13138,7 +13407,7 @@ snapshots: dependencies: '@aws-sdk/client-cloudwatch': 3.841.0 '@aws-sdk/client-dynamodb': 3.840.0 - '@aws-sdk/client-s3': 3.842.0 + '@aws-sdk/client-s3': 3.931.0 '@aws-sdk/client-ssm': 3.840.0 '@aws-sdk/credential-providers': 3.840.0 '@aws-sdk/lib-dynamodb': 3.840.0(@aws-sdk/client-dynamodb@3.840.0) @@ -13519,70 +13788,70 @@ snapshots: '@polka/url@1.0.0-next.24': {} - '@rollup/rollup-android-arm-eabi@4.52.4': + '@rollup/rollup-android-arm-eabi@4.53.2': optional: true - '@rollup/rollup-android-arm64@4.52.4': + '@rollup/rollup-android-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-arm64@4.52.4': + '@rollup/rollup-darwin-arm64@4.53.2': optional: true - '@rollup/rollup-darwin-x64@4.52.4': + '@rollup/rollup-darwin-x64@4.53.2': optional: true - '@rollup/rollup-freebsd-arm64@4.52.4': + '@rollup/rollup-freebsd-arm64@4.53.2': optional: true - '@rollup/rollup-freebsd-x64@4.52.4': + '@rollup/rollup-freebsd-x64@4.53.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.4': + '@rollup/rollup-linux-arm-musleabihf@4.53.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.4': + '@rollup/rollup-linux-arm64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.4': + '@rollup/rollup-linux-arm64-musl@4.53.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.4': + '@rollup/rollup-linux-loong64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.4': + '@rollup/rollup-linux-ppc64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.4': + '@rollup/rollup-linux-riscv64-musl@4.53.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.4': + '@rollup/rollup-linux-s390x-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.4': + '@rollup/rollup-linux-x64-gnu@4.53.2': optional: true - '@rollup/rollup-linux-x64-musl@4.52.4': + '@rollup/rollup-linux-x64-musl@4.53.2': optional: true - '@rollup/rollup-openharmony-arm64@4.52.4': + '@rollup/rollup-openharmony-arm64@4.53.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.4': + '@rollup/rollup-win32-arm64-msvc@4.53.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.4': + '@rollup/rollup-win32-ia32-msvc@4.53.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.4': + '@rollup/rollup-win32-x64-gnu@4.53.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.4': + '@rollup/rollup-win32-x64-msvc@4.53.2': optional: true '@sec-ant/readable-stream@0.4.1': {} @@ -13634,22 +13903,17 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/abort-controller@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/abort-controller@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.6.2 - '@smithy/chunked-blob-reader-native@4.0.0': + '@smithy/chunked-blob-reader-native@4.2.1': dependencies: '@smithy/util-base64': 4.3.0 tslib: 2.6.2 - '@smithy/chunked-blob-reader@5.0.0': + '@smithy/chunked-blob-reader@5.2.0': dependencies: tslib: 2.6.2 @@ -13661,14 +13925,6 @@ snapshots: '@smithy/util-middleware': 3.0.3 tslib: 2.6.2 - '@smithy/config-resolver@4.3.3': - dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 - tslib: 2.6.2 - '@smithy/config-resolver@4.4.3': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -13689,20 +13945,7 @@ snapshots: '@smithy/util-middleware': 3.0.3 tslib: 2.6.2 - '@smithy/core@3.17.0': - dependencies: - '@smithy/middleware-serde': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.3 - '@smithy/util-utf8': 4.2.0 - '@smithy/uuid': 1.1.0 - tslib: 2.6.2 - - '@smithy/core@3.18.0': + '@smithy/core@3.18.3': dependencies: '@smithy/middleware-serde': 4.2.5 '@smithy/protocol-http': 5.3.5 @@ -13725,10 +13968,10 @@ snapshots: '@smithy/credential-provider-imds@4.2.3': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 tslib: 2.6.2 '@smithy/credential-provider-imds@4.2.5': @@ -13739,34 +13982,34 @@ snapshots: '@smithy/url-parser': 4.2.5 tslib: 2.6.2 - '@smithy/eventstream-codec@4.0.4': + '@smithy/eventstream-codec@4.2.5': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 '@smithy/util-hex-encoding': 4.2.0 tslib: 2.6.2 - '@smithy/eventstream-serde-browser@4.0.4': + '@smithy/eventstream-serde-browser@4.2.5': dependencies: - '@smithy/eventstream-serde-universal': 4.0.4 - '@smithy/types': 4.8.0 + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@smithy/eventstream-serde-config-resolver@4.1.2': + '@smithy/eventstream-serde-config-resolver@4.3.5': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@smithy/eventstream-serde-node@4.0.4': + '@smithy/eventstream-serde-node@4.2.5': dependencies: - '@smithy/eventstream-serde-universal': 4.0.4 - '@smithy/types': 4.8.0 + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 - '@smithy/eventstream-serde-universal@4.0.4': + '@smithy/eventstream-serde-universal@4.2.5': dependencies: - '@smithy/eventstream-codec': 4.0.4 - '@smithy/types': 4.8.0 + '@smithy/eventstream-codec': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@smithy/fetch-http-handler@3.2.4': @@ -13777,14 +14020,6 @@ snapshots: '@smithy/util-base64': 3.0.0 tslib: 2.6.2 - '@smithy/fetch-http-handler@5.3.4': - dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/querystring-builder': 4.2.3 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - tslib: 2.6.2 - '@smithy/fetch-http-handler@5.3.6': dependencies: '@smithy/protocol-http': 5.3.5 @@ -13793,11 +14028,11 @@ snapshots: '@smithy/util-base64': 4.3.0 tslib: 2.6.2 - '@smithy/hash-blob-browser@4.0.4': + '@smithy/hash-blob-browser@4.2.6': dependencies: - '@smithy/chunked-blob-reader': 5.0.0 - '@smithy/chunked-blob-reader-native': 4.0.0 - '@smithy/types': 4.8.0 + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@smithy/hash-node@3.0.3': @@ -13807,13 +14042,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 - '@smithy/hash-node@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.6.2 - '@smithy/hash-node@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -13821,9 +14049,9 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 - '@smithy/hash-stream-node@4.0.4': + '@smithy/hash-stream-node@4.2.5': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 @@ -13832,11 +14060,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/invalid-dependency@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/invalid-dependency@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -13854,9 +14077,9 @@ snapshots: dependencies: tslib: 2.6.2 - '@smithy/md5-js@4.0.4': + '@smithy/md5-js@4.2.5': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 '@smithy/util-utf8': 4.2.0 tslib: 2.6.2 @@ -13874,13 +14097,13 @@ snapshots: '@smithy/middleware-compression@4.1.12': dependencies: - '@smithy/core': 3.17.0 + '@smithy/core': 3.18.3 '@smithy/is-array-buffer': 4.2.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 + '@smithy/util-middleware': 4.2.5 '@smithy/util-utf8': 4.2.0 fflate: 0.8.1 tslib: 2.6.2 @@ -13891,12 +14114,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/middleware-content-length@4.2.3': - dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/middleware-content-length@4.2.5': dependencies: '@smithy/protocol-http': 5.3.5 @@ -13913,20 +14130,9 @@ snapshots: '@smithy/util-middleware': 3.0.3 tslib: 2.6.2 - '@smithy/middleware-endpoint@4.3.4': - dependencies: - '@smithy/core': 3.17.0 - '@smithy/middleware-serde': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-middleware': 4.2.3 - tslib: 2.6.2 - - '@smithy/middleware-endpoint@4.3.7': + '@smithy/middleware-endpoint@4.3.10': dependencies: - '@smithy/core': 3.18.0 + '@smithy/core': 3.18.3 '@smithy/middleware-serde': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/shared-ini-file-loader': 4.4.0 @@ -13947,24 +14153,12 @@ snapshots: tslib: 2.6.2 uuid: 9.0.1 - '@smithy/middleware-retry@4.4.4': - dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/service-error-classification': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/uuid': 1.1.0 - tslib: 2.6.2 - - '@smithy/middleware-retry@4.4.7': + '@smithy/middleware-retry@4.4.10': dependencies: '@smithy/node-config-provider': 4.3.5 '@smithy/protocol-http': 5.3.5 '@smithy/service-error-classification': 4.2.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -13976,12 +14170,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/middleware-serde@4.2.3': - dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/middleware-serde@4.2.5': dependencies: '@smithy/protocol-http': 5.3.5 @@ -13993,11 +14181,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/middleware-stack@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/middleware-stack@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -14010,13 +14193,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/node-config-provider@4.3.3': - dependencies: - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/node-config-provider@4.3.5': dependencies: '@smithy/property-provider': 4.2.5 @@ -14032,14 +14208,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/node-http-handler@4.4.2': - dependencies: - '@smithy/abort-controller': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/querystring-builder': 4.2.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/node-http-handler@4.4.5': dependencies: '@smithy/abort-controller': 4.2.5 @@ -14060,7 +14228,7 @@ snapshots: '@smithy/property-provider@4.2.3': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@smithy/property-provider@4.2.5': @@ -14073,11 +14241,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/protocol-http@5.3.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/protocol-http@5.3.5': dependencies: '@smithy/types': 4.9.0 @@ -14089,12 +14252,6 @@ snapshots: '@smithy/util-uri-escape': 3.0.0 tslib: 2.6.2 - '@smithy/querystring-builder@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - '@smithy/util-uri-escape': 4.2.0 - tslib: 2.6.2 - '@smithy/querystring-builder@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -14106,11 +14263,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/querystring-parser@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/querystring-parser@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -14120,10 +14272,6 @@ snapshots: dependencies: '@smithy/types': 3.3.0 - '@smithy/service-error-classification@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - '@smithy/service-error-classification@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -14133,11 +14281,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/shared-ini-file-loader@4.3.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/shared-ini-file-loader@4.4.0': dependencies: '@smithy/types': 4.9.0 @@ -14154,17 +14297,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 - '@smithy/signature-v4@5.3.3': - dependencies: - '@smithy/is-array-buffer': 4.2.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-uri-escape': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.6.2 - '@smithy/signature-v4@5.3.5': dependencies: '@smithy/is-array-buffer': 4.2.0 @@ -14185,20 +14317,10 @@ snapshots: '@smithy/util-stream': 3.1.3 tslib: 2.6.2 - '@smithy/smithy-client@4.9.0': - dependencies: - '@smithy/core': 3.17.0 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-stack': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.3 - tslib: 2.6.2 - - '@smithy/smithy-client@4.9.3': + '@smithy/smithy-client@4.9.6': dependencies: - '@smithy/core': 3.18.0 - '@smithy/middleware-endpoint': 4.3.7 + '@smithy/core': 3.18.3 + '@smithy/middleware-endpoint': 4.3.10 '@smithy/middleware-stack': 4.2.5 '@smithy/protocol-http': 5.3.5 '@smithy/types': 4.9.0 @@ -14213,10 +14335,6 @@ snapshots: dependencies: tslib: 2.6.2 - '@smithy/types@4.8.0': - dependencies: - tslib: 2.6.2 - '@smithy/types@4.9.0': dependencies: tslib: 2.6.2 @@ -14227,12 +14345,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/url-parser@4.2.3': - dependencies: - '@smithy/querystring-parser': 4.2.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/url-parser@4.2.5': dependencies: '@smithy/querystring-parser': 4.2.5 @@ -14298,17 +14410,10 @@ snapshots: bowser: 2.11.0 tslib: 2.6.2 - '@smithy/util-defaults-mode-browser@4.3.3': - dependencies: - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - - '@smithy/util-defaults-mode-browser@4.3.6': + '@smithy/util-defaults-mode-browser@4.3.9': dependencies: '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 tslib: 2.6.2 @@ -14322,23 +14427,13 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/util-defaults-mode-node@4.2.4': - dependencies: - '@smithy/config-resolver': 4.3.3 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - - '@smithy/util-defaults-mode-node@4.2.9': + '@smithy/util-defaults-mode-node@4.2.12': dependencies: '@smithy/config-resolver': 4.4.3 '@smithy/credential-provider-imds': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.3 + '@smithy/smithy-client': 4.9.6 '@smithy/types': 4.9.0 tslib: 2.6.2 @@ -14348,12 +14443,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/util-endpoints@3.2.3': - dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/util-endpoints@3.2.5': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -14373,11 +14462,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/util-middleware@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/util-middleware@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -14389,12 +14473,6 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/util-retry@4.2.3': - dependencies: - '@smithy/service-error-classification': 4.2.3 - '@smithy/types': 4.8.0 - tslib: 2.6.2 - '@smithy/util-retry@4.2.5': dependencies: '@smithy/service-error-classification': 4.2.5 @@ -14412,17 +14490,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 - '@smithy/util-stream@4.5.3': - dependencies: - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.2 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-utf8': 4.2.0 - tslib: 2.6.2 - '@smithy/util-stream@4.5.6': dependencies: '@smithy/fetch-http-handler': 5.3.6 @@ -14463,10 +14530,10 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@smithy/util-waiter@4.0.6': + '@smithy/util-waiter@4.2.5': dependencies: - '@smithy/abort-controller': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/abort-controller': 4.2.5 + '@smithy/types': 4.9.0 tslib: 2.6.2 '@smithy/uuid@1.1.0': @@ -14478,13 +14545,13 @@ snapshots: '@storybook/addon-a11y@10.0.7(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))': dependencies: '@storybook/global': 5.0.0 - axe-core: 4.8.2 + axe-core: 4.11.0 storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) - '@storybook/addon-docs@10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.52.4)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': + '@storybook/addon-docs@10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': dependencies: '@mdx-js/react': 3.1.0(@types/react@18.3.1)(react@18.3.1) - '@storybook/csf-plugin': 10.0.7(esbuild@0.25.5)(rollup@4.52.4)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) + '@storybook/csf-plugin': 10.0.7(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) '@storybook/icons': 1.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/react-dom-shim': 10.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) react: 18.3.1 @@ -14592,13 +14659,13 @@ snapshots: storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.0.3)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) ts-dedent: 2.2.0 - '@storybook/csf-plugin@10.0.7(esbuild@0.25.5)(rollup@4.52.4)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': + '@storybook/csf-plugin@10.0.7(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': dependencies: storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) unplugin: 2.3.10 optionalDependencies: esbuild: 0.25.5 - rollup: 4.52.4 + rollup: 4.53.2 vite: 6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2) webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) @@ -15518,10 +15585,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) + '@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/scope-manager': 8.22.0 '@typescript-eslint/type-utils': 8.22.0(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/utils': 8.22.0(eslint@8.57.1)(typescript@5.5.3) @@ -15573,6 +15640,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 8.22.0 + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1 + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.22.0 @@ -17513,22 +17592,22 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1) object.assign: 4.1.7 object.entries: 1.1.7 semver: 6.3.1 - eslint-config-airbnb-typescript@17.0.0(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3))(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-typescript@17.0.0(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3))(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: - '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) + '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) + '@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1) eslint-config-prettier@9.1.0(eslint@8.57.1): dependencies: @@ -17546,15 +17625,15 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 4.4.3(supports-color@8.1.1) enhanced-resolve: 5.18.3 eslint: 8.57.1 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.3 - get-tsconfig: 4.7.2 + get-tsconfig: 4.13.0 is-core-module: 2.16.1 is-glob: 4.0.3 transitivePeerDependencies: @@ -17580,14 +17659,24 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.5.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color @@ -17632,7 +17721,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 @@ -17642,7 +17731,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -17659,6 +17748,33 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1): + dependencies: + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.5.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: array-includes: 3.1.9 @@ -17726,10 +17842,10 @@ snapshots: object.fromentries: 2.0.8 semver: 6.3.1 - eslint-plugin-jsx-expressions@1.3.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3): + eslint-plugin-jsx-expressions@1.3.1(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3): dependencies: '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) + '@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 tsutils: 3.21.0(typescript@5.5.3) optionalDependencies: @@ -19805,7 +19921,7 @@ snapshots: magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.5.4 make-dir@3.1.0: dependencies: @@ -20539,7 +20655,7 @@ snapshots: postcss-styled-syntax@0.6.3(postcss@8.4.38): dependencies: postcss: 8.4.38 - typescript: 5.5.3 + typescript: 5.9.3 postcss-value-parser@4.2.0: {} @@ -20953,32 +21069,32 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.52.4: + rollup@4.53.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.4 - '@rollup/rollup-android-arm64': 4.52.4 - '@rollup/rollup-darwin-arm64': 4.52.4 - '@rollup/rollup-darwin-x64': 4.52.4 - '@rollup/rollup-freebsd-arm64': 4.52.4 - '@rollup/rollup-freebsd-x64': 4.52.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 - '@rollup/rollup-linux-arm-musleabihf': 4.52.4 - '@rollup/rollup-linux-arm64-gnu': 4.52.4 - '@rollup/rollup-linux-arm64-musl': 4.52.4 - '@rollup/rollup-linux-loong64-gnu': 4.52.4 - '@rollup/rollup-linux-ppc64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-musl': 4.52.4 - '@rollup/rollup-linux-s390x-gnu': 4.52.4 - '@rollup/rollup-linux-x64-gnu': 4.52.4 - '@rollup/rollup-linux-x64-musl': 4.52.4 - '@rollup/rollup-openharmony-arm64': 4.52.4 - '@rollup/rollup-win32-arm64-msvc': 4.52.4 - '@rollup/rollup-win32-ia32-msvc': 4.52.4 - '@rollup/rollup-win32-x64-gnu': 4.52.4 - '@rollup/rollup-win32-x64-msvc': 4.52.4 + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 fsevents: 2.3.3 router@2.2.0: @@ -22269,7 +22385,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.4 + rollup: 4.53.2 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.17.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fbb97180128..efc6cf22ecd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,4 @@ packages: - 'dotcom-rendering' - 'ab-testing' - 'ab-testing/frontend' + - 'ab-testing/deploy-dictionary-lambda' From 8661b688b112efe11bca05b5d704d427f5a42b6d Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 17:04:42 +0000 Subject: [PATCH 02/31] fix lint --- .github/workflows/ab-testing-ci.yml | 10 +- ab-testing/eslint.config.mjs | 7 +- pnpm-lock.yaml | 741 ++++++++++++++++++++++++++-- pnpm-workspace.yaml | 2 +- 4 files changed, 705 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index a86127cb982..72b20d3f48d 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: ab-testing/deploy-dictionary-lambda + working-directory: ab-testing/dictionary-deploy-lambda permissions: contents: read steps: @@ -76,7 +76,7 @@ jobs: uses: actions/upload-artifact@v5 with: name: ab-testing-lambda-build - path: ab-testing/deploy-dictionary-lambda/dist + path: ab-testing/dictionary-deploy-lambda/dist build-ui: name: UI build @@ -138,7 +138,7 @@ jobs: uses: actions/download-artifact@v6.0.0 with: name: ab-testing-lambda-build - path: ab-testing/deploy-dictionary-lambda/dist + path: ab-testing/dictionary-deploy-lambda/dist - name: CDK Test run: pnpm cdk:test @@ -156,8 +156,8 @@ jobs: contentDirectories: | ab-testing: - ab-testing/dist - ab-testing-deploy-dictionary-lambda: - - ab-testing/deploy-dictionary-lambda/dist + ab-testing-dictionary-deploy-lambda: + - ab-testing/dictionary-deploy-lambda/dist admin/ab-testing: - ab-testing/frontend/output/ab-tests.html cdk.out: diff --git a/ab-testing/eslint.config.mjs b/ab-testing/eslint.config.mjs index 5d3ac179d99..b2e76c921e3 100644 --- a/ab-testing/eslint.config.mjs +++ b/ab-testing/eslint.config.mjs @@ -2,7 +2,12 @@ import guardian from "@guardian/eslint-config"; import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ - globalIgnores(["frontend", "eslint.config.mjs", "index.ts"]), + globalIgnores([ + "frontend", + "eslint.config.mjs", + "index.ts", + "dictionary-deploy-lambda/dist", + ]), ...guardian.configs.recommended, { languageOptions: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aef98992b8c..d111455404f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) '@guardian/eslint-config': specifier: 12.0.1 - version: 12.0.1(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) + version: 12.0.1(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) '@guardian/tsconfig': specifier: 1.0.1 version: 1.0.1 @@ -72,6 +72,73 @@ importers: specifier: 5.9.3 version: 5.9.3 + ab-testing/dictionary-deploy-lambda: + dependencies: + '@rollup/plugin-json': + specifier: 6.1.0 + version: 6.1.0(rollup@4.53.2) + cfn-response: + specifier: 1.0.1 + version: 1.0.1 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + rollup-plugin-esbuild: + specifier: 6.2.1 + version: 6.2.1(esbuild@0.27.0)(rollup@4.53.2) + superstruct: + specifier: 2.0.2 + version: 2.0.2 + devDependencies: + '@aws-sdk/client-s3': + specifier: 3.931.0 + version: 3.931.0 + '@guardian/cdk': + specifier: 62.0.1 + version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) + '@guardian/eslint-config': + specifier: 12.0.1 + version: 12.0.1(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) + '@guardian/tsconfig': + specifier: 1.0.1 + version: 1.0.1 + '@rollup/plugin-commonjs': + specifier: 29.0.0 + version: 29.0.0(rollup@4.53.2) + '@rollup/plugin-node-resolve': + specifier: 16.0.3 + version: 16.0.3(rollup@4.53.2) + '@rollup/plugin-typescript': + specifier: 12.3.0 + version: 12.3.0(rollup@4.53.2)(tslib@2.8.1)(typescript@5.9.3) + '@types/aws-lambda': + specifier: 8.10.158 + version: 8.10.158 + '@types/cfn-response': + specifier: 1.0.8 + version: 1.0.8 + '@types/node': + specifier: 22.17.0 + version: 22.17.0 + aws-cdk-lib: + specifier: 2.220.0 + version: 2.220.0(constructs@10.4.2) + aws-lambda: + specifier: 1.0.7 + version: 1.0.7 + eslint: + specifier: 9.39.1 + version: 9.39.1 + prettier: + specifier: 3.0.3 + version: 3.0.3 + rollup: + specifier: 4.53.2 + version: 4.53.2 + typescript: + specifier: 5.9.3 + version: 5.9.3 + ab-testing/frontend: devDependencies: '@guardian/ab-testing': @@ -301,7 +368,7 @@ importers: version: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) webpack-cli: specifier: 6.0.1 - version: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) + version: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) webpack-dev-server: specifier: 5.2.2 version: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) @@ -409,13 +476,13 @@ importers: version: 10.0.7(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) '@storybook/addon-docs': specifier: 10.0.7 - version: 10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) + version: 10.0.7(@types/react@18.3.1)(esbuild@0.27.0)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) '@storybook/addon-webpack5-compiler-swc': specifier: 4.0.2 version: 4.0.2(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(webpack@5.102.1) '@storybook/react-webpack5': specifier: 10.0.7 - version: 10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) + version: 10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) '@svgr/webpack': specifier: 8.1.0 version: 8.1.0(typescript@5.5.3) @@ -520,13 +587,13 @@ importers: version: 0.0.6 '@types/webpack-bundle-analyzer': specifier: 4.7.0 - version: 4.7.0(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + version: 4.7.0(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) '@types/webpack-env': specifier: 1.18.8 version: 1.18.8 '@types/webpack-node-externals': specifier: 3.0.4 - version: 3.0.4(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + version: 3.0.4(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) '@types/youtube': specifier: 0.0.50 version: 0.0.50 @@ -757,7 +824,7 @@ importers: version: 4.2.3 webpack: specifier: 5.102.1 - version: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + version: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) webpack-assets-manifest: specifier: 6.3.0 version: 6.3.0(webpack@5.102.1) @@ -2083,6 +2150,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -2095,6 +2168,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -2107,6 +2186,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -2119,6 +2204,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -2131,6 +2222,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -2143,6 +2240,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -2155,6 +2258,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -2167,6 +2276,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -2179,6 +2294,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -2191,6 +2312,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -2203,6 +2330,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -2215,6 +2348,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -2227,6 +2366,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -2239,6 +2384,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -2251,6 +2402,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -2263,6 +2420,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -2275,12 +2438,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.5': resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -2293,12 +2468,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.5': resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -2311,6 +2498,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -2323,6 +2522,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -2335,6 +2540,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -2347,6 +2558,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -2359,6 +2576,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-plugin-eslint-comments@4.4.1': resolution: {integrity: sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2894,6 +3117,55 @@ packages: '@polka/url@1.0.0-next.24': resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + '@rollup/plugin-commonjs@29.0.0': + resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-typescript@12.3.0': + resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0||^4.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.53.2': resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} cpu: [arm] @@ -3946,6 +4218,9 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/aws-lambda@8.10.158': + resolution: {integrity: sha512-v/n2WsL1ksRKigfqZ9ff7ANobfT3t/T8kI8UOiur98tREwFulv9lRv+pDrocGPWOe3DpD2Y2GKRO+OiyxwgaCQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3964,6 +4239,9 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + '@types/cfn-response@1.0.8': + resolution: {integrity: sha512-b9YttPRwTVcn/r65BcxrXqrVXpBtns3IlwPgE5XfzFoXBSBhimw7ZTbpvdsP39nsxVTmvV7IUN34/9FBQyfe2w==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -4163,6 +4441,9 @@ packages: '@types/relateurl@0.2.33': resolution: {integrity: sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==} + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/resolve@1.20.6': resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} @@ -4840,6 +5121,10 @@ packages: engines: {node: '>= 18.0.0'} hasBin: true + aws-lambda@1.0.7: + resolution: {integrity: sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w==} + hasBin: true + aws-sdk@2.1692.0: resolution: {integrity: sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==} engines: {node: '>= 10.0.0'} @@ -5105,6 +5390,9 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + cfn-response@1.0.1: + resolution: {integrity: sha512-mHfgHUcGpIbmioV/gQ//5V+b8em/ZKFwAgzmbycflJ1q0AU66wlhIgarkYc4JXGgIca+0lxg+XEKO4ZwyZedaw==} + chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} @@ -5278,6 +5566,9 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -5905,6 +6196,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -6186,6 +6482,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -7038,6 +7337,9 @@ packages: is-mobile@3.1.1: resolution: {integrity: sha512-RRoXXR2HNFxNkUnxtaBdGBXtFlUMFa06S0NUKf/LCF+MuGLu13gi9iBCkoEmc6+rpXuwi5Mso5V8Zf7mNynMBQ==} + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -7084,6 +7386,9 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -8168,6 +8473,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} @@ -8647,6 +8955,13 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rollup-plugin-esbuild@6.2.1: + resolution: {integrity: sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==} + engines: {node: '>=14.18.0'} + peerDependencies: + esbuild: '>=0.18.0' + rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 + rollup@4.53.2: resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -9596,6 +9911,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-utils@0.2.5: + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} + engines: {node: '>=18.12.0'} + unplugin@1.16.1: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} @@ -12916,144 +13235,222 @@ snapshots: '@esbuild/aix-ppc64@0.25.5': optional: true + '@esbuild/aix-ppc64@0.27.0': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true '@esbuild/android-arm64@0.25.5': optional: true + '@esbuild/android-arm64@0.27.0': + optional: true + '@esbuild/android-arm@0.18.20': optional: true '@esbuild/android-arm@0.25.5': optional: true + '@esbuild/android-arm@0.27.0': + optional: true + '@esbuild/android-x64@0.18.20': optional: true '@esbuild/android-x64@0.25.5': optional: true + '@esbuild/android-x64@0.27.0': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true '@esbuild/darwin-arm64@0.25.5': optional: true + '@esbuild/darwin-arm64@0.27.0': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true '@esbuild/darwin-x64@0.25.5': optional: true + '@esbuild/darwin-x64@0.27.0': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true '@esbuild/freebsd-arm64@0.25.5': optional: true + '@esbuild/freebsd-arm64@0.27.0': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true '@esbuild/freebsd-x64@0.25.5': optional: true + '@esbuild/freebsd-x64@0.27.0': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true '@esbuild/linux-arm64@0.25.5': optional: true + '@esbuild/linux-arm64@0.27.0': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true '@esbuild/linux-arm@0.25.5': optional: true + '@esbuild/linux-arm@0.27.0': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true '@esbuild/linux-ia32@0.25.5': optional: true + '@esbuild/linux-ia32@0.27.0': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true '@esbuild/linux-loong64@0.25.5': optional: true + '@esbuild/linux-loong64@0.27.0': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true '@esbuild/linux-mips64el@0.25.5': optional: true + '@esbuild/linux-mips64el@0.27.0': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true '@esbuild/linux-ppc64@0.25.5': optional: true + '@esbuild/linux-ppc64@0.27.0': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true '@esbuild/linux-riscv64@0.25.5': optional: true + '@esbuild/linux-riscv64@0.27.0': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true '@esbuild/linux-s390x@0.25.5': optional: true + '@esbuild/linux-s390x@0.27.0': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true '@esbuild/linux-x64@0.25.5': optional: true + '@esbuild/linux-x64@0.27.0': + optional: true + '@esbuild/netbsd-arm64@0.25.5': optional: true + '@esbuild/netbsd-arm64@0.27.0': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true '@esbuild/netbsd-x64@0.25.5': optional: true + '@esbuild/netbsd-x64@0.27.0': + optional: true + '@esbuild/openbsd-arm64@0.25.5': optional: true + '@esbuild/openbsd-arm64@0.27.0': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true '@esbuild/openbsd-x64@0.25.5': optional: true + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true '@esbuild/sunos-x64@0.25.5': optional: true + '@esbuild/sunos-x64@0.27.0': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true '@esbuild/win32-arm64@0.25.5': optional: true + '@esbuild/win32-arm64@0.27.0': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true '@esbuild/win32-ia32@0.25.5': optional: true + '@esbuild/win32-ia32@0.27.0': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true '@esbuild/win32-x64@0.25.5': optional: true + '@esbuild/win32-x64@0.27.0': + optional: true + '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.39.1)': dependencies: escape-string-regexp: 4.0.0 @@ -13244,13 +13641,13 @@ snapshots: '@guardian/eslint-config-typescript@12.0.0(eslint@8.57.1)(tslib@2.6.2)(typescript@5.5.3)': dependencies: - '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2) + '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2) '@stylistic/eslint-plugin': 2.6.2(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) tslib: 2.6.2 typescript: 5.5.3 transitivePeerDependencies: @@ -13279,12 +13676,33 @@ snapshots: - supports-color - typescript - '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2)': + '@guardian/eslint-config@12.0.1(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.39.1) + '@eslint/js': 9.19.0 + '@stylistic/eslint-plugin': 2.11.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + eslint-config-prettier: 9.1.0(eslint@9.39.1) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import-x@4.6.1(eslint@9.39.1)(typescript@5.9.3))(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1) + eslint-plugin-import-x: 4.6.1(eslint@9.39.1)(typescript@5.9.3) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1) + eslint-plugin-react: 7.37.2(eslint@9.39.1) + eslint-plugin-react-hooks: 5.1.0(eslint@9.39.1) + eslint-plugin-storybook: 0.11.1(eslint@9.39.1)(typescript@5.9.3) + globals: 15.14.0 + read-package-up: 11.0.0 + typescript-eslint: 8.22.0(eslint@9.39.1)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-plugin-import + - supports-color + - typescript + + '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2)': dependencies: eslint: 8.57.1 eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) tslib: 2.6.2 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -13788,6 +14206,51 @@ snapshots: '@polka/url@1.0.0-next.24': {} + '@rollup/plugin-commonjs@29.0.0(rollup@4.53.2)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.3) + is-reference: 1.2.1 + magic-string: 0.30.17 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.2 + + '@rollup/plugin-json@6.1.0(rollup@4.53.2)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + optionalDependencies: + rollup: 4.53.2 + + '@rollup/plugin-node-resolve@16.0.3(rollup@4.53.2)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.53.2 + + '@rollup/plugin-typescript@12.3.0(rollup@4.53.2)(tslib@2.8.1)(typescript@5.9.3)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + resolve: 1.22.10 + typescript: 5.9.3 + optionalDependencies: + rollup: 4.53.2 + tslib: 2.8.1 + + '@rollup/pluginutils@5.3.0(rollup@4.53.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.2 + '@rollup/rollup-android-arm-eabi@4.53.2': optional: true @@ -14548,10 +15011,10 @@ snapshots: axe-core: 4.11.0 storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) - '@storybook/addon-docs@10.0.7(@types/react@18.3.1)(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': + '@storybook/addon-docs@10.0.7(@types/react@18.3.1)(esbuild@0.27.0)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': dependencies: '@mdx-js/react': 3.1.0(@types/react@18.3.1)(react@18.3.1) - '@storybook/csf-plugin': 10.0.7(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) + '@storybook/csf-plugin': 10.0.7(esbuild@0.27.0)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1) '@storybook/icons': 1.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/react-dom-shim': 10.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) react: 18.3.1 @@ -14595,7 +15058,7 @@ snapshots: - '@swc/helpers' - webpack - '@storybook/builder-webpack5@10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': + '@storybook/builder-webpack5@10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': dependencies: '@storybook/core-webpack': 10.0.7(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) case-sensitive-paths-webpack-plugin: 2.4.0 @@ -14607,9 +15070,9 @@ snapshots: magic-string: 0.30.17 storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) style-loader: 4.0.0(webpack@5.102.1) - terser-webpack-plugin: 5.3.14(@swc/core@1.13.5)(esbuild@0.25.5)(webpack@5.102.1) + terser-webpack-plugin: 5.3.14(@swc/core@1.13.5)(esbuild@0.27.0)(webpack@5.102.1) ts-dedent: 2.2.0 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) webpack-dev-middleware: 6.1.3(webpack@5.102.1) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 @@ -14659,15 +15122,15 @@ snapshots: storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.0.3)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) ts-dedent: 2.2.0 - '@storybook/csf-plugin@10.0.7(esbuild@0.25.5)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': + '@storybook/csf-plugin@10.0.7(esbuild@0.27.0)(rollup@4.53.2)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))(webpack@5.102.1)': dependencies: storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) unplugin: 2.3.10 optionalDependencies: - esbuild: 0.25.5 + esbuild: 0.27.0 rollup: 4.53.2 vite: 6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2) - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) '@storybook/csf-plugin@9.1.13(storybook@9.1.13(@testing-library/dom@10.4.1)(prettier@3.0.3)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))': dependencies: @@ -14690,7 +15153,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/preset-react-webpack@10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': + '@storybook/preset-react-webpack@10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': dependencies: '@storybook/core-webpack': 10.0.7(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2))) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.5.3)(webpack@5.102.1) @@ -14703,7 +15166,7 @@ snapshots: semver: 7.5.4 storybook: 10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) tsconfig-paths: 4.2.0 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: @@ -14763,10 +15226,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 9.1.13(@testing-library/dom@10.4.1)(prettier@3.0.3)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)) - '@storybook/react-webpack5@10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': + '@storybook/react-webpack5@10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1)': dependencies: - '@storybook/builder-webpack5': 10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) - '@storybook/preset-react-webpack': 10.0.7(@swc/core@1.13.5)(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) + '@storybook/builder-webpack5': 10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) + '@storybook/preset-react-webpack': 10.0.7(@swc/core@1.13.5)(esbuild@0.27.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3)(webpack-cli@6.0.1) '@storybook/react': 10.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.0.7(@testing-library/dom@10.4.1)(prettier@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)(tsx@4.6.2)))(typescript@5.5.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -15166,6 +15629,8 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/aws-lambda@8.10.158': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.4 @@ -15196,6 +15661,10 @@ snapshots: dependencies: '@types/node': 22.17.0 + '@types/cfn-response@1.0.8': + dependencies: + '@types/aws-lambda': 8.10.158 + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -15432,6 +15901,8 @@ snapshots: '@types/relateurl@0.2.33': {} + '@types/resolve@1.20.2': {} + '@types/resolve@1.20.6': {} '@types/response-time@2.3.8': @@ -15513,11 +15984,11 @@ snapshots: '@types/uuid@9.0.8': {} - '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1)': + '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1)': dependencies: '@types/node': 22.17.0 tapable: 2.2.1 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -15526,10 +15997,10 @@ snapshots: '@types/webpack-env@1.18.8': {} - '@types/webpack-node-externals@3.0.4(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1)': + '@types/webpack-node-externals@3.0.4(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1)': dependencies: '@types/node': 22.17.0 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -16044,17 +16515,17 @@ snapshots: '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': dependencies: webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': dependencies: webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.102.1)': dependencies: webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) optionalDependencies: webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) @@ -16387,6 +16858,13 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + aws-lambda@1.0.7: + dependencies: + aws-sdk: 2.1692.0 + commander: 3.0.2 + js-yaml: 3.14.1 + watchpack: 2.4.4 + aws-sdk@2.1692.0: dependencies: buffer: 4.9.2 @@ -16708,6 +17186,8 @@ snapshots: ccount@2.0.1: {} + cfn-response@1.0.1: {} + chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -16871,6 +17351,8 @@ snapshots: commander@2.20.3: {} + commander@3.0.2: {} + commander@6.2.1: {} commander@7.2.0: {} @@ -17045,7 +17527,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.5.4 optionalDependencies: - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) css-select@4.3.0: dependencies: @@ -17572,6 +18054,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.5 '@esbuild/win32-x64': 0.25.5 + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -17625,13 +18136,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1): dependencies: debug: 4.4.3(supports-color@8.1.1) enhanced-resolve: 5.18.3 eslint: 8.57.1 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) fast-glob: 3.3.3 get-tsconfig: 4.13.0 is-core-module: 2.16.1 @@ -17659,14 +18170,31 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import-x@4.6.1(eslint@9.39.1)(typescript@5.9.3))(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3(supports-color@8.1.1) + enhanced-resolve: 5.18.3 + eslint: 9.39.1 + fast-glob: 3.3.3 + get-tsconfig: 4.13.0 + is-bun-module: 1.3.0 + is-glob: 4.0.3 + stable-hash: 0.0.4 + optionalDependencies: + eslint-plugin-import: 2.29.1(eslint@9.39.1) + eslint-plugin-import-x: 4.6.1(eslint@9.39.1)(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -17721,7 +18249,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): dependencies: array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 @@ -17731,7 +18259,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -17803,6 +18331,32 @@ snapshots: - supports-color optional: true + eslint-plugin-import@2.29.1(eslint@9.39.1): + dependencies: + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + optional: true + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1): dependencies: aria-query: 5.3.2 @@ -18097,6 +18651,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -18446,7 +19002,7 @@ snapshots: semver: 7.5.4 tapable: 2.3.0 typescript: 5.5.3 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) form-data-encoder@2.1.4: {} @@ -19113,6 +19669,8 @@ snapshots: is-mobile@3.1.1: {} + is-module@1.0.0: {} + is-negative-zero@2.0.3: {} is-network-error@1.3.0: {} @@ -19142,6 +19700,10 @@ snapshots: is-promise@4.0.0: {} + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.8 + is-reference@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -20547,6 +21109,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + pathval@2.0.0: {} peek-readable@5.3.1: {} @@ -21069,6 +21633,17 @@ snapshots: dependencies: glob: 7.2.3 + rollup-plugin-esbuild@6.2.1(esbuild@0.27.0)(rollup@4.53.2): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + es-module-lexer: 1.7.0 + esbuild: 0.27.0 + get-tsconfig: 4.13.0 + rollup: 4.53.2 + unplugin-utils: 0.2.5 + transitivePeerDependencies: + - supports-color + rollup@4.53.2: dependencies: '@types/estree': 1.0.8 @@ -21700,7 +22275,7 @@ snapshots: style-loader@4.0.0(webpack@5.102.1): dependencies: - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) stylelint-config-recommended@14.0.0(stylelint@16.5.0(typescript@5.5.3)): dependencies: @@ -21826,7 +22401,7 @@ snapshots: dependencies: '@swc/core': 1.13.5 '@swc/counter': 0.1.3 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) swr@1.3.0(react@18.3.1): dependencies: @@ -21866,6 +22441,18 @@ snapshots: '@swc/core': 1.13.5 esbuild: 0.25.5 + terser-webpack-plugin@5.3.14(@swc/core@1.13.5)(esbuild@0.27.0)(webpack@5.102.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.44.0 + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) + optionalDependencies: + '@swc/core': 1.13.5 + esbuild: 0.27.0 + terser@5.26.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -22278,6 +22865,11 @@ snapshots: unpipe@1.0.0: {} + unplugin-utils@0.2.5: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + unplugin@1.16.1: dependencies: acorn: 8.15.0 @@ -22440,7 +23032,7 @@ snapshots: lockfile: 1.0.4 schema-utils: 4.3.3 tapable: 2.3.0 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) webpack-bundle-analyzer@4.10.2: dependencies: @@ -22474,12 +23066,31 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) webpack-merge: 6.0.1 optionalDependencies: webpack-bundle-analyzer: 4.10.2 webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) + webpack-cli@6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1): + dependencies: + '@discoveryjs/json-ext': 0.6.3 + '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) + '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) + '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.102.1) + colorette: 2.0.20 + commander: 12.1.0 + cross-spawn: 7.0.6 + envinfo: 7.14.0 + fastest-levenshtein: 1.0.16 + import-local: 3.2.0 + interpret: 3.1.1 + rechoir: 0.8.0 + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack-merge: 6.0.1 + optionalDependencies: + webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) + webpack-dev-middleware@6.1.3(webpack@5.102.1): dependencies: colorette: 2.0.20 @@ -22510,7 +23121,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) webpack-dev-server@5.2.2(webpack-cli@6.0.1)(webpack@5.102.1): dependencies: @@ -22544,7 +23155,7 @@ snapshots: ws: 8.18.3 optionalDependencies: webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) transitivePeerDependencies: - bufferutil - debug @@ -22566,7 +23177,7 @@ snapshots: debug: 3.2.7 require-from-string: 2.0.2 source-map-support: 0.5.21 - webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.25.5)(webpack-cli@6.0.1) + webpack: 5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1) transitivePeerDependencies: - supports-color @@ -22626,6 +23237,40 @@ snapshots: terser-webpack-plugin: 5.3.14(@swc/core@1.13.5)(esbuild@0.25.5)(webpack@5.102.1) watchpack: 2.4.4 webpack-sources: 3.3.3 + optionalDependencies: + webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + webpack@5.102.1(@swc/core@1.13.5)(esbuild@0.27.0)(webpack-cli@6.0.1): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.26.3 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.14(@swc/core@1.13.5)(esbuild@0.27.0)(webpack@5.102.1) + watchpack: 2.4.4 + webpack-sources: 3.3.3 optionalDependencies: webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efc6cf22ecd..79d7dc83243 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,4 +3,4 @@ packages: - 'dotcom-rendering' - 'ab-testing' - 'ab-testing/frontend' - - 'ab-testing/deploy-dictionary-lambda' + - 'ab-testing/dictionary-deploy-lambda' From c26e301b39950b083c5a506612cb3da7fcac6ddb Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 17:14:39 +0000 Subject: [PATCH 03/31] fastly secrets --- .github/workflows/ab-testing-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 72b20d3f48d..99d424ce864 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -23,8 +23,8 @@ jobs: run: working-directory: ab-testing env: - FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }} - FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }} + FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }} + FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }} steps: - uses: actions/checkout@v5 From 74d0107be198da70c8dc23a17e8172535150ced4 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 17:27:46 +0000 Subject: [PATCH 04/31] missing cdk --- ab-testing/package.json | 1 + pnpm-lock.yaml | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ab-testing/package.json b/ab-testing/package.json index 940b15202bb..0c373be7051 100644 --- a/ab-testing/package.json +++ b/ab-testing/package.json @@ -28,6 +28,7 @@ "@guardian/eslint-config": "12.0.1", "@guardian/tsconfig": "1.0.1", "@types/node": "22.17.0", + "aws-cdk": "2.1030.0", "aws-cdk-lib": "2.220.0", "eslint": "9.39.1", "prettier": "3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d111455404f..88ad048e8f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,13 +49,16 @@ importers: version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) '@guardian/eslint-config': specifier: 12.0.1 - version: 12.0.1(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) + version: 12.0.1(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) '@guardian/tsconfig': specifier: 1.0.1 version: 1.0.1 '@types/node': specifier: 22.17.0 version: 22.17.0 + aws-cdk: + specifier: 2.1030.0 + version: 2.1030.0 aws-cdk-lib: specifier: 2.220.0 version: 2.220.0(constructs@10.4.2) @@ -98,7 +101,7 @@ importers: version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) '@guardian/eslint-config': specifier: 12.0.1 - version: 12.0.1(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.22.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) + version: 12.0.1(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) '@guardian/tsconfig': specifier: 1.0.1 version: 1.0.1 From 6210367bac08d88c2019ef28476c7f48d33cd252 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 17:35:24 +0000 Subject: [PATCH 05/31] build artifact download --- .github/workflows/ab-testing-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 99d424ce864..cf7522649d0 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -127,6 +127,7 @@ jobs: uses: actions/download-artifact@v6.0.0 with: name: ab-testing-build + path: ab-testing/dist - name: Fetch UI build uses: actions/download-artifact@v6.0.0 From 18755d3e5ba03757982a89c6ae4ae389704a9903 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 17:41:59 +0000 Subject: [PATCH 06/31] cacheControl required --- .github/workflows/ab-testing-ci.yml | 14 +++++++------- ab-testing/cdk/bin/cdk.ts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index cf7522649d0..892d10e15e2 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -16,9 +16,9 @@ on: - '.github/workflows/ab-testing-*.yml' jobs: - build: + dictionaries-ci: runs-on: ubuntu-latest - name: Build + name: Dictionaries CI defaults: run: working-directory: ab-testing @@ -55,8 +55,8 @@ jobs: name: ab-testing-build path: ab-testing/dist - build-lambda: - name: Build Lambda + lambda-ci: + name: Deploy Lambda CI runs-on: ubuntu-latest defaults: run: @@ -78,8 +78,8 @@ jobs: name: ab-testing-lambda-build path: ab-testing/dictionary-deploy-lambda/dist - build-ui: - name: UI build + ui-ci: + name: UI CI runs-on: ubuntu-latest defaults: run: @@ -104,7 +104,7 @@ jobs: riff-raff: runs-on: ubuntu-latest - needs: [build, build-ui, build-lambda] + needs: [dictionaries-ci, ui-ci, lambda-ci] permissions: id-token: write contents: read diff --git a/ab-testing/cdk/bin/cdk.ts b/ab-testing/cdk/bin/cdk.ts index 9d2f2caab19..eee7f301355 100644 --- a/ab-testing/cdk/bin/cdk.ts +++ b/ab-testing/cdk/bin/cdk.ts @@ -40,6 +40,7 @@ deployments.set(abTestingArtifactDeployment, { stacks: new Set(["frontend"]), parameters: { bucketSsmKey: "/account/services/dotcom-store.bucket", + cacheControl: "public, max-age=315360000", prefixStack: false, publicReadAcl: false, }, From e8830161f74e8c73d183e6c3162bc96017849dac Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Mon, 17 Nov 2025 18:25:20 +0000 Subject: [PATCH 07/31] more riff-raff tweaks --- .github/workflows/ab-testing-ci.yml | 18 ++++++++++++------ ab-testing/cdk/bin/cdk.ts | 17 ++++++++--------- .../dictionary-deploy-lambda/src/deploy.ts | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 892d10e15e2..b3bbe2156e1 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -72,11 +72,17 @@ jobs: - name: Build Lambda run: pnpm build + - name: Zip app artifact + run: | + cd dist + zip -r lambda.zip . + zip -j lambda.zip ../package.json + - name: Save build uses: actions/upload-artifact@v5 with: name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist + path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip ui-ci: name: UI CI @@ -139,7 +145,7 @@ jobs: uses: actions/download-artifact@v6.0.0 with: name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist + path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip - name: CDK Test run: pnpm cdk:test @@ -155,11 +161,11 @@ jobs: projectName: dotcom:ab-testing configPath: ab-testing/cdk.out/riff-raff.yaml contentDirectories: | - ab-testing: + ab-testing-config-artifacts: - ab-testing/dist - ab-testing-dictionary-deploy-lambda: - - ab-testing/dictionary-deploy-lambda/dist - admin/ab-testing: + ab-testing-deploy: + - ab-testing/dictionary-deploy-lambda/dist/lambda.zip + ab-testing-ui-artifact: - ab-testing/frontend/output/ab-tests.html cdk.out: - ab-testing/cdk.out diff --git a/ab-testing/cdk/bin/cdk.ts b/ab-testing/cdk/bin/cdk.ts index eee7f301355..58e958a445c 100644 --- a/ab-testing/cdk/bin/cdk.ts +++ b/ab-testing/cdk/bin/cdk.ts @@ -30,11 +30,9 @@ const { riffRaffYaml: { deployments }, } = riffRaff; -const abTestingArtifactDeployment = "ab-testing-dictionary-artifact"; - -deployments.set(abTestingArtifactDeployment, { - app: abTestingArtifactDeployment, - contentDirectory: "dictionary-deploy-lambda/artifacts", +deployments.set("config/ab-testing", { + app: "ab-testing-config-artifact", + contentDirectory: "ab-testing-config-artifacts", type: "aws-s3", regions: new Set(["eu-west-1"]), stacks: new Set(["frontend"]), @@ -46,9 +44,9 @@ deployments.set(abTestingArtifactDeployment, { }, }); -deployments.set("ab-testing-ui-artifact", { +deployments.set("admin/ab-testing", { app: "ab-testing-ui-artifact", - contentDirectory: "admin/ab-testing", + contentDirectory: "ab-testing-ui-artifact", type: "aws-s3", regions: new Set(["eu-west-1"]), stacks: new Set(["frontend"]), @@ -60,8 +58,9 @@ deployments.set("ab-testing-ui-artifact", { }, }); +// We need the test artifacts in place before deploying the lambda that uses them deployments - .get(`lambda-update-eu-west-1-frontend-${appName}`) - ?.dependencies?.push(abTestingArtifactDeployment); + .get(`cfn-eu-west-1-frontend-dictionary-deploy-lambda`) + ?.dependencies?.push("config/ab-testing"); riffRaff.synth(); diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts index 4203d087b23..3590654367b 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -4,7 +4,7 @@ import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; const ARTIFACT_BUCKET_NAME = process.env.ARTIFACT_BUCKET_NAME ?? ""; const STAGE = process.env.STAGE ?? "CODE"; -const CONFIG_PREFIX = `/${STAGE}/config/ab-tests`; +const CONFIG_PREFIX = `/${STAGE}/config/ab-testing`; type ServiceInfo = { activeVersion: { number: number }; From 8341e1a3529e07d35d5141d9e14193bed663ab1c Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 11:54:55 +0000 Subject: [PATCH 08/31] fastly params are secure strings --- .../dictionaryDeployLambda.test.ts.snap | 24 ++++----------- ab-testing/cdk/lib/dictionaryDeployLambda.ts | 29 +++++++++++++------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap index 693b8d4b4a3..d529f18006f 100644 --- a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap @@ -12,11 +12,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/dotcom-store.bucket" }, - "SsmParameterValueabtestingdeployCODEfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/ab-testing-deploy/CODE/fastly-api-token" - }, - "SsmParameterValueabtestingdeployCODEfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter": { + "FastlyAbTestingConfigParameterParameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/ab-testing-deploy/CODE/fastly-ab-testing-config" }, @@ -224,11 +220,9 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` }, "Environment": { "Variables": { - "FASTLY_API_TOKEN": { - "Ref": "SsmParameterValueabtestingdeployCODEfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter" - }, + "FASTLY_API_TOKEN": "{{resolve:ssm-secure:/ab-testing-deploy/CODE/fastly-api-token}}", "FASTLY_AB_TESTING_CONFIG": { - "Ref": "SsmParameterValueabtestingdeployCODEfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "FastlyAbTestingConfigParameterParameter" }, "STAGE": "CODE", "ARTIFACT_BUCKET_NAME": { @@ -311,11 +305,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/dotcom-store.bucket" }, - "SsmParameterValueabtestingdeployPRODfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/ab-testing-deploy/PROD/fastly-api-token" - }, - "SsmParameterValueabtestingdeployPRODfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter": { + "FastlyAbTestingConfigParameterParameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/ab-testing-deploy/PROD/fastly-ab-testing-config" }, @@ -523,11 +513,9 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` }, "Environment": { "Variables": { - "FASTLY_API_TOKEN": { - "Ref": "SsmParameterValueabtestingdeployPRODfastlyapitokenC96584B6F00A464EAD1953AFF4B05118Parameter" - }, + "FASTLY_API_TOKEN": "{{resolve:ssm-secure:/ab-testing-deploy/PROD/fastly-api-token}}", "FASTLY_AB_TESTING_CONFIG": { - "Ref": "SsmParameterValueabtestingdeployPRODfastlyabtestingconfigC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "FastlyAbTestingConfigParameterParameter" }, "STAGE": "PROD", "ARTIFACT_BUCKET_NAME": { diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.ts b/ab-testing/cdk/lib/dictionaryDeployLambda.ts index 760680e8650..368a0a21b46 100644 --- a/ab-testing/cdk/lib/dictionaryDeployLambda.ts +++ b/ab-testing/cdk/lib/dictionaryDeployLambda.ts @@ -26,6 +26,24 @@ export class DictionaryDeployLambda extends GuStack { ), ); + const fastlyApiKeyParameter = + StringParameter.fromSecureStringParameterAttributes( + this, + "FastlyApiKeyParameter", + { + parameterName: `/${app}/${this.stage}/fastly-api-token`, + }, + ); + + const fastlyConfigParameter = + StringParameter.fromStringParameterAttributes( + this, + "FastlyAbTestingConfigParameter", + { + parameterName: `/${app}/${this.stage}/fastly-ab-testing-config`, + }, + ); + const lambda = new GuLambdaFunction(this, "ID5BatonLambda", { functionName: `${app}-${this.stage}`, fileName: "lambda.zip", @@ -34,15 +52,8 @@ export class DictionaryDeployLambda extends GuStack { runtime: Runtime.NODEJS_22_X, memorySize: 256, environment: { - FASTLY_API_TOKEN: StringParameter.valueForStringParameter( - this, - `/${app}/${this.stage}/fastly-api-token`, - ), - FASTLY_AB_TESTING_CONFIG: - StringParameter.valueForStringParameter( - this, - `/${app}/${this.stage}/fastly-ab-testing-config`, - ), + FASTLY_API_TOKEN: fastlyApiKeyParameter.stringValue, + FASTLY_AB_TESTING_CONFIG: fastlyConfigParameter.stringValue, STAGE: this.stage, ARTIFACT_BUCKET_NAME: s3Bucket.bucketName, }, From 96b34937b7ef0da5fa2cdf18c0c08ec6533f2bc5 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 13:11:27 +0000 Subject: [PATCH 09/31] need to read params directly from SSM --- ab-testing/cdk/lib/dictionaryDeployLambda.ts | 4 ++-- ab-testing/package.json | 3 ++- pnpm-lock.yaml | 12 +++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.ts b/ab-testing/cdk/lib/dictionaryDeployLambda.ts index 368a0a21b46..8057538b8b2 100644 --- a/ab-testing/cdk/lib/dictionaryDeployLambda.ts +++ b/ab-testing/cdk/lib/dictionaryDeployLambda.ts @@ -52,14 +52,14 @@ export class DictionaryDeployLambda extends GuStack { runtime: Runtime.NODEJS_22_X, memorySize: 256, environment: { - FASTLY_API_TOKEN: fastlyApiKeyParameter.stringValue, - FASTLY_AB_TESTING_CONFIG: fastlyConfigParameter.stringValue, STAGE: this.stage, ARTIFACT_BUCKET_NAME: s3Bucket.bucketName, }, }); s3Bucket.grantRead(lambda); + fastlyApiKeyParameter.grantRead(lambda); + fastlyConfigParameter.grantRead(lambda); // Trigger the Lambda to run upon deployment new CustomResource(this, "InvokeDictionaryDeployLambda", { diff --git a/ab-testing/package.json b/ab-testing/package.json index 0c373be7051..2a79be3cba2 100644 --- a/ab-testing/package.json +++ b/ab-testing/package.json @@ -10,7 +10,7 @@ "build": "node scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json", "build-ui": "pnpm --filter @guardian/ab-testing-frontend run build", "lambda:build": "pnpm --filter @guardian/ab-testing-lambda run build", - "test": "node --test", + "test": "node --test --test-reporter spec scripts/**/*.test.ts lib/**/*.test.ts", "lint": "eslint .", "prettier:check": "prettier . --check --cache", "prettier:fix": "prettier . --write --cache", @@ -24,6 +24,7 @@ }, "devDependencies": { "@aws-sdk/client-s3": "3.931.0", + "@aws-sdk/client-ssm": "3.621.0", "@guardian/cdk": "62.0.1", "@guardian/eslint-config": "12.0.1", "@guardian/tsconfig": "1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88ad048e8f2..976d3fca29d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@aws-sdk/client-s3': specifier: 3.931.0 version: 3.931.0 + '@aws-sdk/client-ssm': + specifier: 3.621.0 + version: 3.621.0 '@guardian/cdk': specifier: 62.0.1 version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) @@ -5268,9 +5271,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - bowser@2.12.1: resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} @@ -12223,7 +12223,7 @@ snapshots: dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - bowser: 2.11.0 + bowser: 2.12.1 tslib: 2.6.2 '@aws-sdk/util-user-agent-browser@3.840.0': @@ -14873,7 +14873,7 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 - bowser: 2.11.0 + bowser: 2.12.1 tslib: 2.6.2 '@smithy/util-defaults-mode-browser@4.3.9': @@ -17060,8 +17060,6 @@ snapshots: boolbase@1.0.0: {} - bowser@2.11.0: {} - bowser@2.12.1: {} brace-expansion@1.1.12: From 1b3e3194a3c1d80b046bc530dc530dc03398bb88 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 13:11:44 +0000 Subject: [PATCH 10/31] refactor fastly client into class based api --- .../src/deploy-dictionary.ts | 42 +-- .../dictionary-deploy-lambda/src/deploy.ts | 26 +- .../dictionary-deploy-lambda/src/index.ts | 66 ++-- ab-testing/lib/fastly-api.test.ts | 321 ------------------ ab-testing/lib/fastly-api.ts | 306 ----------------- ab-testing/lib/fastly/client.test.ts | 285 ++++++++++++++++ ab-testing/lib/fastly/client.ts | 225 ++++++++++++ ab-testing/lib/fastly/dictionary.test.ts | 183 ++++++++++ ab-testing/lib/fastly/dictionary.ts | 54 +++ ab-testing/lib/fastly/service.test.ts | 104 ++++++ ab-testing/lib/fastly/service.ts | 65 ++++ ab-testing/lib/fastly/utils.test.ts | 147 ++++++++ ab-testing/lib/fastly/utils.ts | 89 +++++ ab-testing/scripts/build/index.ts | 7 +- 14 files changed, 1208 insertions(+), 712 deletions(-) delete mode 100644 ab-testing/lib/fastly-api.test.ts delete mode 100644 ab-testing/lib/fastly-api.ts create mode 100644 ab-testing/lib/fastly/client.test.ts create mode 100644 ab-testing/lib/fastly/client.ts create mode 100644 ab-testing/lib/fastly/dictionary.test.ts create mode 100644 ab-testing/lib/fastly/dictionary.ts create mode 100644 ab-testing/lib/fastly/service.test.ts create mode 100644 ab-testing/lib/fastly/service.ts create mode 100644 ab-testing/lib/fastly/utils.test.ts create mode 100644 ab-testing/lib/fastly/utils.ts diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts b/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts index 83bddb1ff09..3e3a8b3f090 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts @@ -1,9 +1,5 @@ -import { - calculateUpdates, - getDictionaryItems, - updateDictionaryItems, - verifyDictionaryName, -} from "../../lib/fastly-api.ts"; +import type { FastlyDictionary } from "../../lib/fastly/dictionary.ts"; +import { calculateUpdates } from "../../lib/fastly/utils.ts"; import type { KeyValue } from "./fetch-artifact.ts"; /** @@ -14,49 +10,27 @@ import type { KeyValue } from "./fetch-artifact.ts"; * @param keyValues An array of key-value pairs to deploy to the dictionary. */ export const deployDictionary = async ( - { - dictionaryName, - dictionaryId, - serviceId, - activeVersion, - }: { - dictionaryName: string; - dictionaryId: string; - serviceId: string; - activeVersion: number; - }, + dictionary: FastlyDictionary, keyValues: KeyValue[], ) => { - await verifyDictionaryName({ - serviceId, - activeVersion, - dictionaryName, - dictionaryId, - }); - - const currentKeyValues = await getDictionaryItems({ - dictionaryId, - }); + const currentKeyValues = await dictionary.getItems(); const updates = calculateUpdates(keyValues, currentKeyValues); if (updates.length === 0) { - console.log(`No key-values need updating in '${dictionaryName}'`); + console.log(`No key-values need updating in '${dictionary.name}'`); } else { Map.groupBy(updates, (item) => item.op).forEach((items, op) => { console.log( - `Performing ${items.length} ${op} operations in '${dictionaryName}'`, + `Performing ${items.length} ${op} operations in '${dictionary.name}'`, ); }); console.log( - `Performing ${updates.length} total operations in '${dictionaryName}'`, + `Performing ${updates.length} total operations in '${dictionary.name}'`, ); - const response = await updateDictionaryItems({ - dictionaryId, - items: updates, - }); + const response = await dictionary.updateItems(updates); if (response.status !== "ok") { throw new Error(`Failed to update mvt groups dictionary`); diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts index 3590654367b..cde9e17f208 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -1,3 +1,4 @@ +import type { FastlyDictionary } from "../../lib/fastly/dictionary.ts"; import { deployDictionary } from "./deploy-dictionary.ts"; import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; @@ -6,15 +7,9 @@ const STAGE = process.env.STAGE ?? "CODE"; const CONFIG_PREFIX = `/${STAGE}/config/ab-testing`; -type ServiceInfo = { - activeVersion: { number: number }; - serviceId: string; -}; - type ArtifactInfo = { artifact: string; - dictionaryName: string; - dictionaryId: string; + dictionary: FastlyDictionary; }; /** @@ -22,26 +17,15 @@ type ArtifactInfo = { * @param serviceInfo The Fastly service information including active version and service ID. * @param deployments An array of artifact deployment information. */ -export const fetchAndDeployArtifacts = async ( - { activeVersion, serviceId }: ServiceInfo, - deployments: ArtifactInfo[], -) => { +export const fetchAndDeployArtifacts = async (deployments: ArtifactInfo[]) => { try { await Promise.all( - deployments.map(({ artifact, dictionaryName, dictionaryId }) => + deployments.map(({ artifact, dictionary }) => fetchDictionaryArtifact( ARTIFACT_BUCKET_NAME, `${CONFIG_PREFIX}/${artifact}`, ).then((abTestGroups) => - deployDictionary( - { - serviceId, - activeVersion: activeVersion.number, - dictionaryName, - dictionaryId, - }, - abTestGroups, - ), + deployDictionary(dictionary, abTestGroups), ), ), ); diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 4e07981f242..d0ecc4ef795 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -1,3 +1,4 @@ +import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; import type { Handler } from "aws-cdk-lib/aws-lambda"; import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; import { send } from "cfn-response"; @@ -9,48 +10,57 @@ import { serviceId, serviceName, } from "../../lib/config.ts"; -import { getService } from "../../lib/fastly-api.ts"; +import { FastlyClient } from "../../lib/fastly/client.ts"; import { fetchAndDeployArtifacts } from "./deploy.ts"; +const ssmClient = new SSMClient({ region: "eu-west-1" }); + +const getSecureString = async (name: string) => { + const response = await ssmClient.send( + new GetParameterCommand({ + Name: name, + WithDecryption: true, + }), + ); + return response.Parameter?.Value; +}; + export const handler: Handler = async ( event: CloudFormationCustomResourceEvent, context: Context, ): Promise => { - const service = await getService(serviceId); - if (service.name !== serviceName) { - throw new Error( - `Service ID ${serviceId} does not match the expected service name ${serviceName}`, - ); - } - - const activeVersion = service.versions.find( - (v: { active: boolean }) => v.active, + const apiToken = await getSecureString( + `/ab-testing-deploy/${process.env.STAGE}/fastly-api-token`, ); - if (!activeVersion) { - throw new Error(`No active version found for service ${service.name}`); + if (!apiToken) { + throw new Error("Fastly API token not found in SSM Parameter Store"); } + const fastly = new FastlyClient(apiToken); + const service = await fastly.getService(serviceId, serviceName); + + const abTestsDictionary = await service.getDictionary( + abTestsDictionaryId, + abTestsDictionaryName, + ); + const mvtDictionary = await service.getDictionary( + mvtDictionaryId, + mvtDictionaryName, + ); + if (event.RequestType === "Create" || event.RequestType === "Update") { try { - await fetchAndDeployArtifacts( + await fetchAndDeployArtifacts([ + { + artifact: "ab-test-groups.json", + dictionary: abTestsDictionary, + }, { - activeVersion, - serviceId, + artifact: "mvt-groups.json", + dictionary: mvtDictionary, }, - [ - { - artifact: "ab-test-groups.json", - dictionaryName: abTestsDictionaryName, - dictionaryId: abTestsDictionaryId, - }, - { - artifact: "mvt-groups.json", - dictionaryName: mvtDictionaryName, - dictionaryId: mvtDictionaryId, - }, - ], - ); + ]); send(event, context, "SUCCESS"); } catch (error) { diff --git a/ab-testing/lib/fastly-api.test.ts b/ab-testing/lib/fastly-api.test.ts deleted file mode 100644 index b00e1a2bc94..00000000000 --- a/ab-testing/lib/fastly-api.test.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { deepEqual, equal, match, rejects } from "node:assert"; -import type { Mock } from "node:test"; -import test, { mock } from "node:test"; - -const mockConfig = { - serviceName: "test-service", - serviceId: "test-service-id", - mvtDictionaryId: "test-mvt-dictionary-id", - mvtDictionaryName: "test-mvt-dictionary", - abTestsDictionaryId: "test-ab-tests-dictionary-id", - abTestsDictionaryName: "test-ab-tests-dictionary", -}; - -// Mock environment variables -process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); -process.env.FASTLY_API_TOKEN = "test-api-token"; - -type MockedFetch = Mock; - -function mockFetch(response: unknown, status = 200, statusText = "OK") { - const mockResponse = new Response(JSON.stringify(response), { - status, - statusText, - headers: { "Content-Type": "application/json" }, - }); - - globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); -} - -// Import after mocking -const { - calculateUpdates, - getDictionaryItems, - getMVTGroupsFromDictionary, - getABTestGroupsFromDictionary, - updateMVTGroups, - updateABTestGroups, - encodeObject, -} = await import("./fastly-api.ts"); - -test("calculateUpdates - creates new items", () => { - const currentDictionary: Array<{ item_key: string; item_value: string }> = - []; - const newDictionary = [ - { item_key: "test1", item_value: "value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - - const result = calculateUpdates(newDictionary, currentDictionary); - - equal(result.length, 2); - deepEqual(result[0], { - item_key: "test1", - item_value: "value1", - op: "create", - }); - deepEqual(result[1], { - item_key: "test2", - item_value: "value2", - op: "create", - }); -}); - -test("calculateUpdates - updates existing items", () => { - const currentDictionary = [ - { item_key: "test1", item_value: "old_value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - const newDictionary = [ - { item_key: "test1", item_value: "new_value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - - const result = calculateUpdates(newDictionary, currentDictionary); - - equal(result.length, 1); - deepEqual(result[0], { - item_key: "test1", - item_value: "new_value1", - op: "update", - }); -}); - -test("calculateUpdates - deletes removed items", () => { - const currentDictionary = [ - { item_key: "test1", item_value: "value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - const newDictionary = [{ item_key: "test1", item_value: "value1" }]; - - const result = calculateUpdates(newDictionary, currentDictionary); - - equal(result.length, 1); - deepEqual(result[0], { - item_key: "test2", - op: "delete", - }); -}); - -test("calculateUpdates - no changes needed", () => { - const currentDictionary = [ - { item_key: "test1", item_value: "value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - const newDictionary = [ - { item_key: "test1", item_value: "value1" }, - { item_key: "test2", item_value: "value2" }, - ]; - - const result = calculateUpdates(newDictionary, currentDictionary); - - equal(result.length, 0); -}); - -test("calculateUpdates - combination of operations", () => { - const currentDictionary = [ - { item_key: "keep", item_value: "same_value" }, - { item_key: "update", item_value: "old_value" }, - { item_key: "delete", item_value: "will_be_deleted" }, - ]; - const newDictionary = [ - { item_key: "keep", item_value: "same_value" }, - { item_key: "update", item_value: "new_value" }, - { item_key: "create", item_value: "new_item" }, - ]; - - const result = calculateUpdates(newDictionary, currentDictionary); - - equal(result.length, 3); - - const deleteOp = result.find((op) => op.op === "delete"); - deepEqual(deleteOp, { - item_key: "delete", - op: "delete", - }); - - const updateOp = result.find((op) => op.op === "update"); - deepEqual(updateOp, { - item_key: "update", - item_value: "new_value", - op: "update", - }); - - const createOp = result.find((op) => op.op === "create"); - deepEqual(createOp, { - item_key: "create", - item_value: "new_item", - op: "create", - }); -}); - -test("getDictionaryItems - fetches and returns dictionary items", async () => { - const mockResponse = [ - { - service_id: "test-service", - item_key: "key1", - item_value: "value1", - dictionary_id: "test-dict", - created_at: "2023-01-01", - updated_at: "2023-01-01", - deleted_at: null, - }, - ]; - - mockFetch(mockResponse); - - const result = await getDictionaryItems({ - dictionaryId: "test-dict", - }); - - deepEqual(result, mockResponse); - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - equal( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[0], - `https://api.fastly.com/service/${mockConfig.serviceId}/dictionary/test-dict/items?per_page=1000`, - ); -}); - -test("getDictionaryItems - throws error on invalid response", async () => { - // Mock invalid response format - mockFetch("not an array"); - - await rejects( - async () => { - await getDictionaryItems({ - dictionaryId: "test-dict", - }); - }, - Error, - "Expected an array", - ); -}); - -test("getDictionaryItems - throws error on fetch failure", async () => { - mockFetch({ error: "Something went wrong" }, 500, "Internal Server Error"); - - await rejects( - async () => { - await getDictionaryItems({ - dictionaryId: "test-dict", - }); - }, - Error, - "Failed to fetch from Fastly: 500", - ); -}); - -test("getMVTGroupsFromDictionary - calls the right endpoint", async () => { - const mockResponse = [] as unknown; - mockFetch(mockResponse); - - await getMVTGroupsFromDictionary(); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - match( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[0] as string, - new RegExp(`/dictionary/${mockConfig.mvtDictionaryId}/items`), - ); -}); - -test("getABTestGroupsFromDictionary - calls the right endpoint", async () => { - const mockResponse = [] as unknown; - mockFetch(mockResponse); - - await getABTestGroupsFromDictionary(); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - match( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[0] as string, - new RegExp(`/dictionary/${mockConfig.abTestsDictionaryId}/items`), - ); -}); - -test("updateMVTGroups - makes PATCH request with correct data", async () => { - mockFetch({ status: "ok" }); - - const items = [ - { item_key: "key1", item_value: "value1", op: "create" as const }, - ]; - await updateMVTGroups(items); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - equal( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]?.method, - "PATCH", - ); - - const requestBody = JSON.parse( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.body as string, - ) as { - items: Array<{ - item_key: string; - item_value: string; - op: "create" | "update" | "delete"; - }>; - }; - - deepEqual(requestBody.items, items); -}); - -test("updateABTestGroups - makes PATCH request with correct data", async () => { - mockFetch({ status: "ok" }); - - const items = [ - { item_key: "key1", item_value: "value1", op: "update" as const }, - ]; - await updateABTestGroups(items); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - equal( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]?.method, - "PATCH", - ); - - const requestBody = JSON.parse( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.body as string, - ) as { - items: Array<{ - item_key: string; - item_value: string; - op: "create" | "update" | "delete"; - }>; - }; - deepEqual(requestBody.items, items); -}); - -test("updateABTestGroups - throws error on non-ok status", async () => { - mockFetch({ status: "error" }); - - const items = [ - { item_key: "key1", item_value: "value1", op: "create" as const }, - ]; - - await rejects( - async () => { - await updateABTestGroups(items); - }, - Error, - "Failed to update dictionary: error", - ); -}); - -test("encodeObject - encodes object to string", () => { - const obj = { - test: "value", - another: 123, - bool: true, - }; - - const result = encodeObject(obj); - equal(result, "test=value,another=123,bool=true"); -}); - -test("encodeObject - handles arrays", () => { - const arr = ["test", "another"]; - - const result = encodeObject(arr); - equal(result, "0=test,1=another"); -}); diff --git a/ab-testing/lib/fastly-api.ts b/ab-testing/lib/fastly-api.ts deleted file mode 100644 index 5cc813f2b88..00000000000 --- a/ab-testing/lib/fastly-api.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { - array, - assert, - boolean, - nullable, - number, - object, - string, - type, -} from "superstruct"; -import { - abTestsDictionaryId, - apiToken, - mvtDictionaryId, - serviceId, -} from "./config.ts"; - -const dictionaryItemStruct = object({ - service_id: string(), - item_key: string(), - item_value: string(), - dictionary_id: string(), - created_at: string(), - updated_at: string(), - deleted_at: nullable(string()), -}); - -type UpdateDictionaryItemRequest = - | { - item_key: string; - item_value: string; - op: "create" | "update" | "upsert"; - } - | { - item_key: string; - op: "delete"; - }; - -const FASTLY_API_BASE_URL = "https://api.fastly.com/service"; - -/** - * Fetch data from Fastly API - * We're using fetch instead of the Fastly API client as it doesn't play nicely with Deno, nor is it typed - * - * @param url - The URL to fetch - * @param options - Fetch options - * @returns The parsed JSON response - * @throws Error if the response is not ok or if the response cannot be parsed - */ -const fetchFromFastly = async ( - url: string, - options: RequestInit = {}, -): Promise => { - const response = await fetch(url, { - ...options, - headers: { - ...options.headers, - "Fastly-Key": apiToken, - }, - }); - if (!response.ok) { - console.error(await response.text()); - throw new Error( - `Failed to fetch from Fastly: ${response.status} ${response.statusText}`, - ); - } - const data = (await response.json()) as unknown; - if (!data) { - throw new Error(`Failed to parse response from Fastly`); - } - return data as T; -}; - -const getService = async (serviceId: string) => { - const service = await fetchFromFastly( - `${FASTLY_API_BASE_URL}/${serviceId}`, - ); - - assert( - service, - type({ - id: string(), - name: string(), - versions: array( - type({ - active: boolean(), - number: number(), - }), - ), - }), - ); - - return service; -}; - -const getDictionary = async ({ - activeVersion, - dictionaryName, - serviceId, -}: { - activeVersion: number; - dictionaryName: string; - serviceId: string; -}) => { - const dictionary = await fetchFromFastly( - `${FASTLY_API_BASE_URL}/${serviceId}/version/${activeVersion}/dictionary/${dictionaryName}`, - ); - assert(dictionary, type({ id: string(), name: string() })); - - return dictionary; -}; - -const getDictionaryItems = async ({ - dictionaryId, -}: { - dictionaryId: string; -}) => { - const dictionary = await fetchFromFastly( - `${FASTLY_API_BASE_URL}/${serviceId}/dictionary/${dictionaryId}/items?per_page=1000`, - ); - - assert(dictionary, array(dictionaryItemStruct)); - - return dictionary; -}; - -const updateDictionaryItems = async ({ - dictionaryId, - items, -}: { - dictionaryId: string; - items: UpdateDictionaryItemRequest[]; -}) => { - const dictionary = await fetchFromFastly( - `${FASTLY_API_BASE_URL}/${serviceId}/dictionary/${dictionaryId}/items`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - service_id: serviceId, - dictionary_id: dictionaryId, - items, - }), - }, - ); - - assert(dictionary, object({ status: string() })); - if (dictionary.status !== "ok") { - throw new Error(`Failed to update dictionary: ${dictionary.status}`); - } - return dictionary; -}; - -/** - * Calculate the bulk updates for a dictionary - * Compare the new dictionary with the current dictionary and return the operations required - * - * @param updatedDictionary - * @param currentDictionary - * @returns - */ -const calculateUpdates = ( - updatedDictionary: Array<{ - item_key: string; - item_value: string; - }>, - currentDictionary: Array<{ - item_key: string; - item_value: string; - }>, -): UpdateDictionaryItemRequest[] => { - const currentDictionaryKeys = new Set( - currentDictionary.map((group) => group.item_key), - ); - - const updateDeleteOps = currentDictionary.reduce<{ - updates: UpdateDictionaryItemRequest[]; - deletes: UpdateDictionaryItemRequest[]; - }>( - (ops, group) => { - const updatedGroup = updatedDictionary.find( - (newGroup) => newGroup.item_key === group.item_key, - ); - - if (!updatedGroup) { - return { - ...ops, - deletes: [ - ...ops.deletes, - { - item_key: group.item_key, - op: "delete", - }, - ], - }; - } - - if (group.item_value !== updatedGroup.item_value) { - return { - ...ops, - updates: [ - ...ops.updates, - { - item_key: group.item_key, - item_value: updatedGroup.item_value, - op: "update", - }, - ], - }; - } - - return ops; - }, - { - updates: [], - deletes: [], - }, - ); - - const createOpts: UpdateDictionaryItemRequest[] = updatedDictionary - .filter((group) => !currentDictionaryKeys.has(group.item_key)) - .map(({ item_key, item_value }) => ({ - item_key, - item_value, - op: "create", - })); - - const bulkUpdates = [ - ...updateDeleteOps.deletes, - ...updateDeleteOps.updates, - ...createOpts, - ]; - - return bulkUpdates; -}; - -/** - * Verify that the service and dictionary names match the expected IDs - * - */ -const verifyDictionaryName = async ({ - serviceId, - activeVersion, - dictionaryName, - dictionaryId, -}: { - serviceId: string; - activeVersion: number; - dictionaryName: string; - dictionaryId: string; -}) => { - const dictionary = await getDictionary({ - serviceId, - activeVersion, - dictionaryName, - }); - - if (dictionary.id !== dictionaryId) { - throw new Error( - `Dictionary ID ${dictionaryId} does not match the expected dictionary ${dictionaryName}`, - ); - } - - return; -}; - -const getMVTGroupsFromDictionary = () => - getDictionaryItems({ - dictionaryId: mvtDictionaryId, - }); - -const getABTestGroupsFromDictionary = () => - getDictionaryItems({ - dictionaryId: abTestsDictionaryId, - }); - -const updateMVTGroups = (items: UpdateDictionaryItemRequest[]) => - updateDictionaryItems({ - dictionaryId: mvtDictionaryId, - items, - }); -const updateABTestGroups = (items: UpdateDictionaryItemRequest[]) => - updateDictionaryItems({ - dictionaryId: abTestsDictionaryId, - items, - }); - -const encodeObject = (obj: Record | string[]) => - Object.entries(obj) - .map(([key, value]) => `${key}=${String(value)}`) - .join(","); - -export { - getMVTGroupsFromDictionary, - getABTestGroupsFromDictionary, - getService, - getDictionaryItems, - updateMVTGroups, - updateABTestGroups, - updateDictionaryItems, - calculateUpdates, - encodeObject, - verifyDictionaryName, -}; diff --git a/ab-testing/lib/fastly/client.test.ts b/ab-testing/lib/fastly/client.test.ts new file mode 100644 index 00000000000..1d03fc40aea --- /dev/null +++ b/ab-testing/lib/fastly/client.test.ts @@ -0,0 +1,285 @@ +import { deepEqual, equal, match, rejects } from "node:assert"; +import type { Mock } from "node:test"; +import test, { describe, mock } from "node:test"; + +const mockConfig = { + serviceName: "test-service", + serviceId: "test-service-id", + mvtDictionaryId: "test-mvt-dictionary-id", + mvtDictionaryName: "test-mvt-dictionary", + abTestsDictionaryId: "test-ab-tests-dictionary-id", + abTestsDictionaryName: "test-ab-tests-dictionary", +}; + +// Mock environment variables +process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); +process.env.FASTLY_API_TOKEN = "test-api-token"; + +type MockedFetch = Mock; + +function mockFetch(response: unknown, status = 200, statusText = "OK") { + const mockResponse = new Response(JSON.stringify(response), { + status, + statusText, + headers: { "Content-Type": "application/json" }, + }); + + globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); +} + +// Import after mocking +const { FastlyClient } = await import("./client.ts"); + +describe("FastlyClient", async () => { + await test("fetch - successfully fetches data", async () => { + const mockResponse = { data: "test" }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const result = await client.fetch("test-endpoint"); + + deepEqual(result, mockResponse); + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + }); + + await test("fetch - throws error on fetch failure", async () => { + mockFetch( + { error: "Something went wrong" }, + 500, + "Internal Server Error", + ); + + const client = new FastlyClient("test-api-token"); + + await rejects( + async () => { + await client.fetch("test-endpoint"); + }, + Error, + "Failed to fetch from Fastly: 500", + ); + }); + + await test("getService - returns service config", async () => { + const mockResponse = { + id: "service-123", + name: "test-service", + versions: [ + { active: false, number: 1 }, + { active: true, number: 2 }, + ], + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const result = await client.getService("service-123", "test-service"); + + deepEqual(result, { + id: "service-123", + name: "test-service", + activeVersion: 2, + client, + }); + }); + + await test("getService - throws error when service name doesn't match", async () => { + const mockResponse = { + id: "service-123", + name: "wrong-service", + versions: [{ active: true, number: 1 }], + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + + await rejects( + async () => { + await client.getService("service-123", "test-service"); + }, + Error, + "Service ID service-123 does not match the expected service name test-service", + ); + }); + + await test("getService - throws error when no active version found", async () => { + const mockResponse = { + id: "service-123", + name: "test-service", + versions: [{ active: false, number: 1 }], + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + + await rejects( + async () => { + await client.getService("service-123", "test-service"); + }, + Error, + "No active version found for service test-service", + ); + }); + + await test("getDictionary - returns dictionary config", async () => { + const mockResponse = { + id: "dict-123", + name: "test-dictionary", + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const result = await client.getDictionary({ + activeVersion: 1, + dictionaryName: "test-dictionary", + serviceId: "service-123", + }); + + deepEqual(result, mockResponse); + match( + (globalThis.fetch as MockedFetch).mock.calls[0] + ?.arguments[0] as string, + /service-123\/version\/1\/dictionary\/test-dictionary/, + ); + }); + + await test("getDictionaryItems - fetches and returns dictionary items", async () => { + const mockResponse = [ + { + service_id: "test-service", + item_key: "key1", + item_value: "value1", + dictionary_id: "test-dict", + created_at: "2023-01-01", + updated_at: "2023-01-01", + deleted_at: null, + }, + ]; + + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const result = await client.getDictionaryItems({ + dictionaryId: "test-dict", + }); + + deepEqual(result, mockResponse); + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + match( + (globalThis.fetch as MockedFetch).mock.calls[0] + ?.arguments[0] as string, + /dictionary\/test-dict\/items\?per_page=1000/, + ); + }); + + await test("getDictionaryItems - throws error on invalid response", async () => { + // Mock invalid response format + mockFetch("not an array"); + + const client = new FastlyClient("test-api-token"); + + await rejects( + async () => { + await client.getDictionaryItems({ + dictionaryId: "test-dict", + }); + }, + Error, + "Expected an array", + ); + }); + + await test("updateDictionaryItems - makes PATCH request with correct data", async () => { + mockFetch({ status: "ok" }); + + const client = new FastlyClient("test-api-token"); + const items = [ + { item_key: "key1", item_value: "value1", op: "create" as const }, + ]; + await client.updateDictionaryItems({ + dictionaryId: "dict-123", + items, + }); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + equal( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.method, + "PATCH", + ); + + const requestBody = JSON.parse( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.body as string, + ) as { + items: Array<{ + item_key: string; + item_value: string; + op: "create" | "update" | "delete"; + }>; + }; + + deepEqual(requestBody.items, items); + }); + + await test("updateDictionaryItems - throws error on non-ok status", async () => { + mockFetch({ status: "error" }); + + const client = new FastlyClient("test-api-token"); + const items = [ + { item_key: "key1", item_value: "value1", op: "create" as const }, + ]; + + await rejects( + async () => { + await client.updateDictionaryItems({ + dictionaryId: "dict-123", + items, + }); + }, + Error, + "Failed to update dictionary: error", + ); + }); + + await test("verifyDictionaryName - succeeds when IDs match", async () => { + const mockResponse = { + id: "dict-123", + name: "test-dictionary", + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + await client.verifyDictionaryName({ + serviceId: "service-123", + activeVersion: 1, + dictionaryName: "test-dictionary", + dictionaryId: "dict-123", + }); + + // No error thrown means success + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + }); + + await test("verifyDictionaryName - throws error when IDs don't match", async () => { + const mockResponse = { + id: "dict-456", + name: "test-dictionary", + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + + await rejects( + async () => { + await client.verifyDictionaryName({ + serviceId: "service-123", + activeVersion: 1, + dictionaryName: "test-dictionary", + dictionaryId: "dict-123", + }); + }, + Error, + "Dictionary ID dict-123 does not match the expected dictionary test-dictionary", + ); + }); +}); diff --git a/ab-testing/lib/fastly/client.ts b/ab-testing/lib/fastly/client.ts new file mode 100644 index 00000000000..ae8741100f7 --- /dev/null +++ b/ab-testing/lib/fastly/client.ts @@ -0,0 +1,225 @@ +import { + array, + assert, + boolean, + nullable, + number, + object, + string, + type, +} from "superstruct"; +import { apiToken, serviceId } from "../config.ts"; +import { FastlyService } from "./service.ts"; + +const dictionaryItemStruct = object({ + service_id: string(), + item_key: string(), + item_value: string(), + dictionary_id: string(), + created_at: string(), + updated_at: string(), + deleted_at: nullable(string()), +}); + +export type DictionaryItem = { + service_id: string; + item_key: string; + item_value: string; + dictionary_id: string; + created_at: string; + updated_at: string; + deleted_at: string | null; +}; + +export type UpdateDictionaryItemRequest = + | { + item_key: string; + item_value: string; + op: "create" | "update" | "upsert"; + } + | { + item_key: string; + op: "delete"; + }; + +export type ServiceConfig = { + id: string; + name: string; + activeVersion: number; +}; + +export class FastlyClient { + apiToken: string; + baseUrl: string = "https://api.fastly.com/service"; + + constructor(apiToken: string, baseUrl?: string) { + this.apiToken = apiToken; + if (baseUrl) { + this.baseUrl = baseUrl; + } + } + /** + * Fetch data from Fastly API + * We're using fetch instead of the Fastly API client as it doesn't play nicely with Deno, nor is it typed + * + * @param url - The URL to fetch + * @param options - Fetch options + * @returns The parsed JSON response + * @throws Error if the response is not ok or if the response cannot be parsed + */ + async fetch(url: string, options: RequestInit = {}): Promise { + const response = await fetch(`${this.baseUrl}/${url}`, { + ...options, + headers: { + ...options.headers, + "Fastly-Key": apiToken, + }, + }); + if (!response.ok) { + console.error(await response.text()); + throw new Error( + `Failed to fetch from Fastly: ${response.status} ${response.statusText}`, + ); + } + const data = (await response.json()) as unknown; + if (!data) { + throw new Error(`Failed to parse response from Fastly`); + } + return data as T; + } + + async getService( + serviceId: string, + serviceName: string, + ): Promise { + const serviceConfig = await this.fetch(serviceId); + + assert( + serviceConfig, + type({ + id: string(), + name: string(), + versions: array( + type({ + active: boolean(), + number: number(), + }), + ), + }), + ); + + if (serviceConfig.name !== serviceName) { + throw new Error( + `Service ID ${serviceId} does not match the expected service name ${serviceName}`, + ); + } + + const activeVersion = serviceConfig.versions.find( + (v: { active: boolean }) => v.active, + ); + + if (!activeVersion) { + throw new Error( + `No active version found for service ${serviceConfig.name}`, + ); + } + + return new FastlyService(this, { + id: serviceConfig.id, + name: serviceConfig.name, + activeVersion: activeVersion.number, + }); + } + + async getDictionary({ + activeVersion, + dictionaryName, + serviceId, + }: { + activeVersion: number; + dictionaryName: string; + serviceId: string; + }): Promise<{ id: string; name: string }> { + const dictionary = await this.fetch( + `${serviceId}/version/${activeVersion}/dictionary/${dictionaryName}`, + ); + assert(dictionary, type({ id: string(), name: string() })); + + return dictionary; + } + + async getDictionaryItems({ + dictionaryId, + }: { + dictionaryId: string; + }): Promise { + const dictionary = await this.fetch( + `${serviceId}/dictionary/${dictionaryId}/items?per_page=1000`, + ); + + assert(dictionary, array(dictionaryItemStruct)); + + return dictionary; + } + + async updateDictionaryItems({ + dictionaryId, + items, + }: { + dictionaryId: string; + items: UpdateDictionaryItemRequest[]; + }): Promise<{ status: string }> { + const dictionary = await this.fetch( + `${serviceId}/dictionary/${dictionaryId}/items`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + service_id: serviceId, + dictionary_id: dictionaryId, + items, + }), + }, + ); + + assert(dictionary, object({ status: string() })); + if (dictionary.status !== "ok") { + throw new Error( + `Failed to update dictionary: ${dictionary.status}`, + ); + } + return dictionary; + } + + /** + * Verify that the service and dictionary names match the expected IDs + * + */ + async verifyDictionaryName({ + serviceId, + activeVersion, + dictionaryName, + dictionaryId, + }: { + serviceId: string; + activeVersion: number; + dictionaryName: string; + dictionaryId: string; + }) { + const dictionary = await this.getDictionary({ + serviceId, + activeVersion, + dictionaryName, + }); + + if (dictionary.id !== dictionaryId) { + throw new Error( + `Dictionary ID ${dictionaryId} does not match the expected dictionary ${dictionaryName}`, + ); + } + + return; + } +} diff --git a/ab-testing/lib/fastly/dictionary.test.ts b/ab-testing/lib/fastly/dictionary.test.ts new file mode 100644 index 00000000000..75708115f1d --- /dev/null +++ b/ab-testing/lib/fastly/dictionary.test.ts @@ -0,0 +1,183 @@ +import { deepEqual, equal, match } from "node:assert"; +import type { Mock } from "node:test"; +import test, { describe, mock } from "node:test"; + +const mockConfig = { + serviceName: "test-service", + serviceId: "test-service-id", + mvtDictionaryId: "test-mvt-dictionary-id", + mvtDictionaryName: "test-mvt-dictionary", + abTestsDictionaryId: "test-ab-tests-dictionary-id", + abTestsDictionaryName: "test-ab-tests-dictionary", +}; + +// Mock environment variables +process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); +process.env.FASTLY_API_TOKEN = "test-api-token"; + +type MockedFetch = Mock; + +function mockFetch(response: unknown, status = 200, statusText = "OK") { + const mockResponse = new Response(JSON.stringify(response), { + status, + statusText, + headers: { "Content-Type": "application/json" }, + }); + + globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); +} + +// Import after mocking +const { FastlyClient } = await import("./client.ts"); +const { + getMVTGroupsFromDictionary, + getABTestGroupsFromDictionary, + updateMVTGroups, + updateABTestGroups, +} = await import("./dictionary.ts"); + +const { FastlyService } = await import("./service.ts"); +const { FastlyDictionary } = await import("./dictionary.ts"); + +describe("FastlyDictionary", async () => { + await test("getItems - calls service.getDictionaryItems", async () => { + const mockResponse = [ + { + service_id: "test-service", + item_key: "key1", + item_value: "value1", + dictionary_id: "dict-123", + created_at: "2023-01-01", + updated_at: "2023-01-01", + deleted_at: null, + }, + ]; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const service = new FastlyService(client, { + id: "service-123", + name: "test-service", + activeVersion: 1, + }); + const dictionary = new FastlyDictionary(service, { + id: "dict-123", + name: "test-dictionary", + }); + + const items = await dictionary.getItems(); + + deepEqual(items, mockResponse); + }); + + await test("updateItems - calls service.updateDictionaryItems", async () => { + mockFetch({ status: "ok" }); + + const client = new FastlyClient("test-api-token"); + const service = new FastlyService(client, { + id: "service-123", + name: "test-service", + activeVersion: 1, + }); + const dictionary = new FastlyDictionary(service, { + id: "dict-123", + name: "test-dictionary", + }); + + const items = [ + { item_key: "key1", item_value: "value1", op: "create" as const }, + ]; + await dictionary.updateItems(items); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + }); + + test("getMVTGroupsFromDictionary - calls the right endpoint", async () => { + const mockResponse = [] as unknown; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + await getMVTGroupsFromDictionary(client); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + match( + (globalThis.fetch as MockedFetch).mock.calls[0] + ?.arguments[0] as string, + new RegExp(`/dictionary/${mockConfig.mvtDictionaryId}/items`), + ); + }); + + test("getABTestGroupsFromDictionary - calls the right endpoint", async () => { + const mockResponse = [] as unknown; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + await getABTestGroupsFromDictionary(client); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + match( + (globalThis.fetch as MockedFetch).mock.calls[0] + ?.arguments[0] as string, + new RegExp(`/dictionary/${mockConfig.abTestsDictionaryId}/items`), + ); + }); + + test("updateMVTGroups - makes PATCH request with correct data", async () => { + mockFetch({ status: "ok" }); + + const client = new FastlyClient("test-api-token"); + const items = [ + { item_key: "key1", item_value: "value1", op: "create" as const }, + ]; + await updateMVTGroups(client, items); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + equal( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.method, + "PATCH", + ); + + const requestBody = JSON.parse( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.body as string, + ) as { + items: Array<{ + item_key: string; + item_value: string; + op: "create" | "update" | "delete"; + }>; + }; + + deepEqual(requestBody.items, items); + }); + + test("updateABTestGroups - makes PATCH request with correct data", async () => { + mockFetch({ status: "ok" }); + + const client = new FastlyClient("test-api-token"); + const items = [ + { item_key: "key1", item_value: "value1", op: "update" as const }, + ]; + await updateABTestGroups(client, items); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + equal( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.method, + "PATCH", + ); + + const requestBody = JSON.parse( + (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] + ?.body as string, + ) as { + items: Array<{ + item_key: string; + item_value: string; + op: "create" | "update" | "delete"; + }>; + }; + deepEqual(requestBody.items, items); + }); +}); diff --git a/ab-testing/lib/fastly/dictionary.ts b/ab-testing/lib/fastly/dictionary.ts new file mode 100644 index 00000000000..5b5d32eed97 --- /dev/null +++ b/ab-testing/lib/fastly/dictionary.ts @@ -0,0 +1,54 @@ +import { abTestsDictionaryId, mvtDictionaryId } from "../config.ts"; +import type { FastlyClient, UpdateDictionaryItemRequest } from "./client.ts"; +import type { FastlyService } from "./service.ts"; + +export class FastlyDictionary { + id: string; + name: string; + service: FastlyService; + + constructor( + service: FastlyService, + { id, name }: { id: string; name: string }, + ) { + this.service = service; + this.id = id; + this.name = name; + } + + async getItems() { + return this.service.getDictionaryItems(this.id); + } + + async updateItems(items: UpdateDictionaryItemRequest[]) { + return this.service.updateDictionaryItems(this.id, items); + } +} + +export const getMVTGroupsFromDictionary = (client: FastlyClient) => + client.getDictionaryItems({ + dictionaryId: mvtDictionaryId, + }); + +export const getABTestGroupsFromDictionary = (client: FastlyClient) => + client.getDictionaryItems({ + dictionaryId: abTestsDictionaryId, + }); + +export const updateMVTGroups = ( + client: FastlyClient, + items: UpdateDictionaryItemRequest[], +) => + client.updateDictionaryItems({ + dictionaryId: mvtDictionaryId, + items, + }); + +export const updateABTestGroups = ( + client: FastlyClient, + items: UpdateDictionaryItemRequest[], +) => + client.updateDictionaryItems({ + dictionaryId: abTestsDictionaryId, + items, + }); diff --git a/ab-testing/lib/fastly/service.test.ts b/ab-testing/lib/fastly/service.test.ts new file mode 100644 index 00000000000..eb19dec5f81 --- /dev/null +++ b/ab-testing/lib/fastly/service.test.ts @@ -0,0 +1,104 @@ +import { deepEqual, equal } from "node:assert"; +import type { Mock } from "node:test"; +import test, { describe, mock } from "node:test"; +import { FastlyDictionary } from "./dictionary.ts"; + +const mockConfig = { + serviceName: "test-service", + serviceId: "test-service-id", + mvtDictionaryId: "test-mvt-dictionary-id", + mvtDictionaryName: "test-mvt-dictionary", + abTestsDictionaryId: "test-ab-tests-dictionary-id", + abTestsDictionaryName: "test-ab-tests-dictionary", +}; + +// Mock environment variables +process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); +process.env.FASTLY_API_TOKEN = "test-api-token"; + +type MockedFetch = Mock; + +function mockFetch(response: unknown, status = 200, statusText = "OK") { + const mockResponse = new Response(JSON.stringify(response), { + status, + statusText, + headers: { "Content-Type": "application/json" }, + }); + + globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); +} + +// Import after mocking +const { FastlyClient } = await import("./client.ts"); +const { FastlyService } = await import("./service.ts"); + +describe("FastlyService", async () => { + await test("getDictionary - returns FastlyDictionary instance", async () => { + const mockResponse = { + id: "dict-123", + name: "test-dictionary", + }; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const service = new FastlyService(client, { + id: "service-123", + name: "test-service", + activeVersion: 1, + }); + + const dictionary = await service.getDictionary( + "dict-123", + "test-dictionary", + ); + + equal(dictionary instanceof FastlyDictionary, true); + equal(dictionary.id, "dict-123"); + equal(dictionary.name, "test-dictionary"); + equal(dictionary.service, service); + }); + + await test("getDictionaryItems - calls client.getDictionaryItems", async () => { + const mockResponse = [ + { + service_id: "test-service", + item_key: "key1", + item_value: "value1", + dictionary_id: "dict-123", + created_at: "2023-01-01", + updated_at: "2023-01-01", + deleted_at: null, + }, + ]; + mockFetch(mockResponse); + + const client = new FastlyClient("test-api-token"); + const service = new FastlyService(client, { + id: "service-123", + name: "test-service", + activeVersion: 1, + }); + + const items = await service.getDictionaryItems("dict-123"); + + deepEqual(items, mockResponse); + }); + + await test("updateDictionaryItems - calls client.updateDictionaryItems", async () => { + mockFetch({ status: "ok" }); + + const client = new FastlyClient("test-api-token"); + const service = new FastlyService(client, { + id: "service-123", + name: "test-service", + activeVersion: 1, + }); + + const items = [ + { item_key: "key1", item_value: "value1", op: "create" as const }, + ]; + await service.updateDictionaryItems("dict-123", items); + + equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); + }); +}); diff --git a/ab-testing/lib/fastly/service.ts b/ab-testing/lib/fastly/service.ts new file mode 100644 index 00000000000..b1cf7837c77 --- /dev/null +++ b/ab-testing/lib/fastly/service.ts @@ -0,0 +1,65 @@ +import type { FastlyClient, UpdateDictionaryItemRequest } from "./client.ts"; +import { FastlyDictionary } from "./dictionary.ts"; + +export class FastlyService { + id: string; + name: string; + activeVersion: number; + client: FastlyClient; + + constructor( + client: FastlyClient, + { + id, + name, + activeVersion, + }: { id: string; name: string; activeVersion: number }, + ) { + this.client = client; + this.id = id; + this.name = name; + this.activeVersion = activeVersion; + } + + async getDictionary(id: string, name: string): Promise { + const dictionaryConfig = await this.client.getDictionary({ + activeVersion: this.activeVersion, + dictionaryName: id, + serviceId: this.id, + }); + + if (dictionaryConfig.name !== name) { + throw new Error( + `Dictionary name ${dictionaryConfig.name} does not match expected name ${name}`, + ); + } + + return new FastlyDictionary(this, dictionaryConfig); + } + + async getDictionaryItems(dictionaryId: string) { + return this.client.getDictionaryItems({ dictionaryId }); + } + + async updateDictionaryItems( + dictionaryId: string, + items: UpdateDictionaryItemRequest[], + ) { + return this.client.updateDictionaryItems({ dictionaryId, items }); + } + + async verifyDictionaryName({ + dictionaryName, + dictionaryId, + }: { + dictionaryName: string; + dictionaryId: string; + }) { + return this.client.verifyDictionaryName({ + serviceId: this.id, + activeVersion: this.activeVersion, + dictionaryName, + dictionaryId, + }); + } +} diff --git a/ab-testing/lib/fastly/utils.test.ts b/ab-testing/lib/fastly/utils.test.ts new file mode 100644 index 00000000000..af50058e16c --- /dev/null +++ b/ab-testing/lib/fastly/utils.test.ts @@ -0,0 +1,147 @@ +import { deepEqual, equal } from "node:assert"; +import test from "node:test"; + +const mockConfig = { + serviceName: "test-service", + serviceId: "test-service-id", + mvtDictionaryId: "test-mvt-dictionary-id", + mvtDictionaryName: "test-mvt-dictionary", + abTestsDictionaryId: "test-ab-tests-dictionary-id", + abTestsDictionaryName: "test-ab-tests-dictionary", +}; + +// Mock environment variables +process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); +process.env.FASTLY_API_TOKEN = "test-api-token"; + +// Import after mocking +const { calculateUpdates, encodeObject } = await import("./utils.ts"); + +test("calculateUpdates - creates new items", () => { + const currentDictionary: Array<{ item_key: string; item_value: string }> = + []; + const newDictionary = [ + { item_key: "test1", item_value: "value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + + const result = calculateUpdates(newDictionary, currentDictionary); + + equal(result.length, 2); + deepEqual(result[0], { + item_key: "test1", + item_value: "value1", + op: "create", + }); + deepEqual(result[1], { + item_key: "test2", + item_value: "value2", + op: "create", + }); +}); + +test("calculateUpdates - updates existing items", () => { + const currentDictionary = [ + { item_key: "test1", item_value: "old_value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + const newDictionary = [ + { item_key: "test1", item_value: "new_value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + + const result = calculateUpdates(newDictionary, currentDictionary); + + equal(result.length, 1); + deepEqual(result[0], { + item_key: "test1", + item_value: "new_value1", + op: "update", + }); +}); + +test("calculateUpdates - deletes removed items", () => { + const currentDictionary = [ + { item_key: "test1", item_value: "value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + const newDictionary = [{ item_key: "test1", item_value: "value1" }]; + + const result = calculateUpdates(newDictionary, currentDictionary); + + equal(result.length, 1); + deepEqual(result[0], { + item_key: "test2", + op: "delete", + }); +}); + +test("calculateUpdates - no changes needed", () => { + const currentDictionary = [ + { item_key: "test1", item_value: "value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + const newDictionary = [ + { item_key: "test1", item_value: "value1" }, + { item_key: "test2", item_value: "value2" }, + ]; + + const result = calculateUpdates(newDictionary, currentDictionary); + + equal(result.length, 0); +}); + +test("calculateUpdates - combination of operations", () => { + const currentDictionary = [ + { item_key: "keep", item_value: "same_value" }, + { item_key: "update", item_value: "old_value" }, + { item_key: "delete", item_value: "will_be_deleted" }, + ]; + const newDictionary = [ + { item_key: "keep", item_value: "same_value" }, + { item_key: "update", item_value: "new_value" }, + { item_key: "create", item_value: "new_item" }, + ]; + + const result = calculateUpdates(newDictionary, currentDictionary); + + equal(result.length, 3); + + const deleteOp = result.find((op) => op.op === "delete"); + deepEqual(deleteOp, { + item_key: "delete", + op: "delete", + }); + + const updateOp = result.find((op) => op.op === "update"); + deepEqual(updateOp, { + item_key: "update", + item_value: "new_value", + op: "update", + }); + + const createOp = result.find((op) => op.op === "create"); + deepEqual(createOp, { + item_key: "create", + item_value: "new_item", + op: "create", + }); +}); + +test("encodeObject - encodes object to string", () => { + const obj = { + test: "value", + another: 123, + bool: true, + }; + + const result = encodeObject(obj); + equal(result, "test=value,another=123,bool=true"); +}); + +test("encodeObject - handles arrays", () => { + const arr = ["test", "another"]; + + const result = encodeObject(arr); + equal(result, "0=test,1=another"); +}); diff --git a/ab-testing/lib/fastly/utils.ts b/ab-testing/lib/fastly/utils.ts new file mode 100644 index 00000000000..c119027ae4c --- /dev/null +++ b/ab-testing/lib/fastly/utils.ts @@ -0,0 +1,89 @@ +import type { UpdateDictionaryItemRequest } from "./client.ts"; + +/** + * Calculate the bulk updates for a dictionary + * Compare the new dictionary with the current dictionary and return the operations required + * + * @param updatedDictionary + * @param currentDictionary + * @returns + */ +export const calculateUpdates = ( + updatedDictionary: Array<{ + item_key: string; + item_value: string; + }>, + currentDictionary: Array<{ + item_key: string; + item_value: string; + }>, +): UpdateDictionaryItemRequest[] => { + const currentDictionaryKeys = new Set( + currentDictionary.map((group) => group.item_key), + ); + + const updateDeleteOps = currentDictionary.reduce<{ + updates: UpdateDictionaryItemRequest[]; + deletes: UpdateDictionaryItemRequest[]; + }>( + (ops, group) => { + const updatedGroup = updatedDictionary.find( + (newGroup) => newGroup.item_key === group.item_key, + ); + + if (!updatedGroup) { + return { + ...ops, + deletes: [ + ...ops.deletes, + { + item_key: group.item_key, + op: "delete", + }, + ], + }; + } + + if (group.item_value !== updatedGroup.item_value) { + return { + ...ops, + updates: [ + ...ops.updates, + { + item_key: group.item_key, + item_value: updatedGroup.item_value, + op: "update", + }, + ], + }; + } + + return ops; + }, + { + updates: [], + deletes: [], + }, + ); + + const createOpts: UpdateDictionaryItemRequest[] = updatedDictionary + .filter((group) => !currentDictionaryKeys.has(group.item_key)) + .map(({ item_key, item_value }) => ({ + item_key, + item_value, + op: "create", + })); + + const bulkUpdates = [ + ...updateDeleteOps.deletes, + ...updateDeleteOps.updates, + ...createOpts, + ]; + + return bulkUpdates; +}; + +export const encodeObject = (obj: Record | string[]) => + Object.entries(obj) + .map(([key, value]) => `${key}=${String(value)}`) + .join(","); diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/scripts/build/index.ts index d6500486902..94851665885 100644 --- a/ab-testing/scripts/build/index.ts +++ b/ab-testing/scripts/build/index.ts @@ -2,7 +2,8 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { parseArgs } from "node:util"; import { activeABtests } from "../../abTests.ts"; -import { getMVTGroupsFromDictionary } from "../../lib/fastly-api.ts"; +import { FastlyClient } from "../../lib/fastly/client.ts"; +import { getMVTGroupsFromDictionary } from "../../lib/fastly/dictionary.ts"; import { parseMVTValue, stringifyMVTValue } from "../../lib/fastly-subfield.ts"; import { buildABTestGroupKeyValues } from "./build-ab-tests-dict.ts"; import { calculateAllSpaceUpdates } from "./calculate-mvt-updates.ts"; @@ -28,7 +29,9 @@ if (!flags["mvts"] || !flags["ab-tests"]) { process.exit(1); } -const mvtGroupsDictionary = await getMVTGroupsFromDictionary(); +const fastly = new FastlyClient(process.env.FASTLY_API_TOKEN ?? ""); + +const mvtGroupsDictionary = await getMVTGroupsFromDictionary(fastly); const mvtGroups = new Map( mvtGroupsDictionary.map(({ item_key, item_value }) => { From 5828acab7b5bf734d08672fc904db7bd93d656df Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 13:17:11 +0000 Subject: [PATCH 11/31] read config from ssm --- .../dictionary-deploy-lambda/src/index.ts | 31 +++-- ab-testing/lib/config.ts | 1 + ab-testing/lib/fastly/client.test.ts | 21 +--- ab-testing/lib/fastly/client.ts | 7 +- ab-testing/lib/fastly/dictionary.test.ts | 119 +----------------- ab-testing/lib/fastly/dictionary.ts | 31 +---- ab-testing/lib/fastly/service.test.ts | 21 +--- ab-testing/lib/fastly/service.ts | 11 +- ab-testing/lib/fastly/utils.test.ts | 17 +-- ab-testing/scripts/build/index.ts | 18 ++- 10 files changed, 67 insertions(+), 210 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index d0ecc4ef795..2091c76105c 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -2,14 +2,8 @@ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; import type { Handler } from "aws-cdk-lib/aws-lambda"; import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; import { send } from "cfn-response"; -import { - abTestsDictionaryId, - abTestsDictionaryName, - mvtDictionaryId, - mvtDictionaryName, - serviceId, - serviceName, -} from "../../lib/config.ts"; +import { assert } from "superstruct"; +import { configStruct } from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; import { fetchAndDeployArtifacts } from "./deploy.ts"; @@ -25,6 +19,18 @@ const getSecureString = async (name: string) => { return response.Parameter?.Value; }; +const getFastlyConfig = async () => { + const stringParam = await getSecureString( + `/ab-testing-deploy/${process.env.STAGE}/fastly-ab-testing-config`, + ); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string is invalid JSON too + const json = JSON.parse(stringParam || "{}") as unknown; + + assert(json, configStruct); + + return json; +}; + export const handler: Handler = async ( event: CloudFormationCustomResourceEvent, context: Context, @@ -37,6 +43,15 @@ export const handler: Handler = async ( throw new Error("Fastly API token not found in SSM Parameter Store"); } + const { + serviceId, + serviceName, + abTestsDictionaryId, + abTestsDictionaryName, + mvtDictionaryId, + mvtDictionaryName, + } = await getFastlyConfig(); + const fastly = new FastlyClient(apiToken); const service = await fastly.getService(serviceId, serviceName); diff --git a/ab-testing/lib/config.ts b/ab-testing/lib/config.ts index dc972c5a66f..a80eb5466e3 100644 --- a/ab-testing/lib/config.ts +++ b/ab-testing/lib/config.ts @@ -40,4 +40,5 @@ export { mvtDictionaryName, abTestsDictionaryId, abTestsDictionaryName, + configStruct, }; diff --git a/ab-testing/lib/fastly/client.test.ts b/ab-testing/lib/fastly/client.test.ts index 1d03fc40aea..153ee44db21 100644 --- a/ab-testing/lib/fastly/client.test.ts +++ b/ab-testing/lib/fastly/client.test.ts @@ -1,19 +1,7 @@ import { deepEqual, equal, match, rejects } from "node:assert"; import type { Mock } from "node:test"; import test, { describe, mock } from "node:test"; - -const mockConfig = { - serviceName: "test-service", - serviceId: "test-service-id", - mvtDictionaryId: "test-mvt-dictionary-id", - mvtDictionaryName: "test-mvt-dictionary", - abTestsDictionaryId: "test-ab-tests-dictionary-id", - abTestsDictionaryName: "test-ab-tests-dictionary", -}; - -// Mock environment variables -process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); -process.env.FASTLY_API_TOKEN = "test-api-token"; +import { FastlyClient } from "./client.ts"; type MockedFetch = Mock; @@ -27,9 +15,6 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") { globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); } -// Import after mocking -const { FastlyClient } = await import("./client.ts"); - describe("FastlyClient", async () => { await test("fetch - successfully fetches data", async () => { const mockResponse = { data: "test" }; @@ -160,6 +145,7 @@ describe("FastlyClient", async () => { const client = new FastlyClient("test-api-token"); const result = await client.getDictionaryItems({ dictionaryId: "test-dict", + serviceId: "service-123", }); deepEqual(result, mockResponse); @@ -181,6 +167,7 @@ describe("FastlyClient", async () => { async () => { await client.getDictionaryItems({ dictionaryId: "test-dict", + serviceId: "service-123", }); }, Error, @@ -197,6 +184,7 @@ describe("FastlyClient", async () => { ]; await client.updateDictionaryItems({ dictionaryId: "dict-123", + serviceId: "service-123", items, }); @@ -233,6 +221,7 @@ describe("FastlyClient", async () => { async () => { await client.updateDictionaryItems({ dictionaryId: "dict-123", + serviceId: "service-123", items, }); }, diff --git a/ab-testing/lib/fastly/client.ts b/ab-testing/lib/fastly/client.ts index ae8741100f7..0b4c62057d2 100644 --- a/ab-testing/lib/fastly/client.ts +++ b/ab-testing/lib/fastly/client.ts @@ -8,7 +8,6 @@ import { string, type, } from "superstruct"; -import { apiToken, serviceId } from "../config.ts"; import { FastlyService } from "./service.ts"; const dictionaryItemStruct = object({ @@ -72,7 +71,7 @@ export class FastlyClient { ...options, headers: { ...options.headers, - "Fastly-Key": apiToken, + "Fastly-Key": this.apiToken, }, }); if (!response.ok) { @@ -150,8 +149,10 @@ export class FastlyClient { async getDictionaryItems({ dictionaryId, + serviceId, }: { dictionaryId: string; + serviceId: string; }): Promise { const dictionary = await this.fetch( `${serviceId}/dictionary/${dictionaryId}/items?per_page=1000`, @@ -164,9 +165,11 @@ export class FastlyClient { async updateDictionaryItems({ dictionaryId, + serviceId, items, }: { dictionaryId: string; + serviceId: string; items: UpdateDictionaryItemRequest[]; }): Promise<{ status: string }> { const dictionary = await this.fetch( diff --git a/ab-testing/lib/fastly/dictionary.test.ts b/ab-testing/lib/fastly/dictionary.test.ts index 75708115f1d..cf3f2b4bcd7 100644 --- a/ab-testing/lib/fastly/dictionary.test.ts +++ b/ab-testing/lib/fastly/dictionary.test.ts @@ -1,19 +1,9 @@ -import { deepEqual, equal, match } from "node:assert"; +import { deepEqual, equal } from "node:assert"; import type { Mock } from "node:test"; import test, { describe, mock } from "node:test"; - -const mockConfig = { - serviceName: "test-service", - serviceId: "test-service-id", - mvtDictionaryId: "test-mvt-dictionary-id", - mvtDictionaryName: "test-mvt-dictionary", - abTestsDictionaryId: "test-ab-tests-dictionary-id", - abTestsDictionaryName: "test-ab-tests-dictionary", -}; - -// Mock environment variables -process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); -process.env.FASTLY_API_TOKEN = "test-api-token"; +import { FastlyClient } from "./client.ts"; +import { FastlyDictionary } from "./dictionary.ts"; +import { FastlyService } from "./service.ts"; type MockedFetch = Mock; @@ -27,18 +17,6 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") { globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); } -// Import after mocking -const { FastlyClient } = await import("./client.ts"); -const { - getMVTGroupsFromDictionary, - getABTestGroupsFromDictionary, - updateMVTGroups, - updateABTestGroups, -} = await import("./dictionary.ts"); - -const { FastlyService } = await import("./service.ts"); -const { FastlyDictionary } = await import("./dictionary.ts"); - describe("FastlyDictionary", async () => { await test("getItems - calls service.getDictionaryItems", async () => { const mockResponse = [ @@ -91,93 +69,4 @@ describe("FastlyDictionary", async () => { equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); }); - - test("getMVTGroupsFromDictionary - calls the right endpoint", async () => { - const mockResponse = [] as unknown; - mockFetch(mockResponse); - - const client = new FastlyClient("test-api-token"); - await getMVTGroupsFromDictionary(client); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - match( - (globalThis.fetch as MockedFetch).mock.calls[0] - ?.arguments[0] as string, - new RegExp(`/dictionary/${mockConfig.mvtDictionaryId}/items`), - ); - }); - - test("getABTestGroupsFromDictionary - calls the right endpoint", async () => { - const mockResponse = [] as unknown; - mockFetch(mockResponse); - - const client = new FastlyClient("test-api-token"); - await getABTestGroupsFromDictionary(client); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - match( - (globalThis.fetch as MockedFetch).mock.calls[0] - ?.arguments[0] as string, - new RegExp(`/dictionary/${mockConfig.abTestsDictionaryId}/items`), - ); - }); - - test("updateMVTGroups - makes PATCH request with correct data", async () => { - mockFetch({ status: "ok" }); - - const client = new FastlyClient("test-api-token"); - const items = [ - { item_key: "key1", item_value: "value1", op: "create" as const }, - ]; - await updateMVTGroups(client, items); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - equal( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.method, - "PATCH", - ); - - const requestBody = JSON.parse( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.body as string, - ) as { - items: Array<{ - item_key: string; - item_value: string; - op: "create" | "update" | "delete"; - }>; - }; - - deepEqual(requestBody.items, items); - }); - - test("updateABTestGroups - makes PATCH request with correct data", async () => { - mockFetch({ status: "ok" }); - - const client = new FastlyClient("test-api-token"); - const items = [ - { item_key: "key1", item_value: "value1", op: "update" as const }, - ]; - await updateABTestGroups(client, items); - - equal((globalThis.fetch as MockedFetch).mock.calls.length, 1); - equal( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.method, - "PATCH", - ); - - const requestBody = JSON.parse( - (globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1] - ?.body as string, - ) as { - items: Array<{ - item_key: string; - item_value: string; - op: "create" | "update" | "delete"; - }>; - }; - deepEqual(requestBody.items, items); - }); }); diff --git a/ab-testing/lib/fastly/dictionary.ts b/ab-testing/lib/fastly/dictionary.ts index 5b5d32eed97..fa11a7c22a5 100644 --- a/ab-testing/lib/fastly/dictionary.ts +++ b/ab-testing/lib/fastly/dictionary.ts @@ -1,5 +1,4 @@ -import { abTestsDictionaryId, mvtDictionaryId } from "../config.ts"; -import type { FastlyClient, UpdateDictionaryItemRequest } from "./client.ts"; +import type { UpdateDictionaryItemRequest } from "./client.ts"; import type { FastlyService } from "./service.ts"; export class FastlyDictionary { @@ -24,31 +23,3 @@ export class FastlyDictionary { return this.service.updateDictionaryItems(this.id, items); } } - -export const getMVTGroupsFromDictionary = (client: FastlyClient) => - client.getDictionaryItems({ - dictionaryId: mvtDictionaryId, - }); - -export const getABTestGroupsFromDictionary = (client: FastlyClient) => - client.getDictionaryItems({ - dictionaryId: abTestsDictionaryId, - }); - -export const updateMVTGroups = ( - client: FastlyClient, - items: UpdateDictionaryItemRequest[], -) => - client.updateDictionaryItems({ - dictionaryId: mvtDictionaryId, - items, - }); - -export const updateABTestGroups = ( - client: FastlyClient, - items: UpdateDictionaryItemRequest[], -) => - client.updateDictionaryItems({ - dictionaryId: abTestsDictionaryId, - items, - }); diff --git a/ab-testing/lib/fastly/service.test.ts b/ab-testing/lib/fastly/service.test.ts index eb19dec5f81..82d02745456 100644 --- a/ab-testing/lib/fastly/service.test.ts +++ b/ab-testing/lib/fastly/service.test.ts @@ -1,20 +1,9 @@ import { deepEqual, equal } from "node:assert"; import type { Mock } from "node:test"; import test, { describe, mock } from "node:test"; +import { FastlyClient } from "./client.ts"; import { FastlyDictionary } from "./dictionary.ts"; - -const mockConfig = { - serviceName: "test-service", - serviceId: "test-service-id", - mvtDictionaryId: "test-mvt-dictionary-id", - mvtDictionaryName: "test-mvt-dictionary", - abTestsDictionaryId: "test-ab-tests-dictionary-id", - abTestsDictionaryName: "test-ab-tests-dictionary", -}; - -// Mock environment variables -process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); -process.env.FASTLY_API_TOKEN = "test-api-token"; +import { FastlyService } from "./service.ts"; type MockedFetch = Mock; @@ -28,12 +17,8 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") { globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse)); } -// Import after mocking -const { FastlyClient } = await import("./client.ts"); -const { FastlyService } = await import("./service.ts"); - describe("FastlyService", async () => { - await test("getDictionary - returns FastlyDictionary instance", async () => { + await test.only("getDictionary - returns FastlyDictionary instance", async () => { const mockResponse = { id: "dict-123", name: "test-dictionary", diff --git a/ab-testing/lib/fastly/service.ts b/ab-testing/lib/fastly/service.ts index b1cf7837c77..1f774842ede 100644 --- a/ab-testing/lib/fastly/service.ts +++ b/ab-testing/lib/fastly/service.ts @@ -38,14 +38,21 @@ export class FastlyService { } async getDictionaryItems(dictionaryId: string) { - return this.client.getDictionaryItems({ dictionaryId }); + return this.client.getDictionaryItems({ + dictionaryId, + serviceId: this.id, + }); } async updateDictionaryItems( dictionaryId: string, items: UpdateDictionaryItemRequest[], ) { - return this.client.updateDictionaryItems({ dictionaryId, items }); + return this.client.updateDictionaryItems({ + dictionaryId, + serviceId: this.id, + items, + }); } async verifyDictionaryName({ diff --git a/ab-testing/lib/fastly/utils.test.ts b/ab-testing/lib/fastly/utils.test.ts index af50058e16c..6b2d08bf6cd 100644 --- a/ab-testing/lib/fastly/utils.test.ts +++ b/ab-testing/lib/fastly/utils.test.ts @@ -1,21 +1,6 @@ import { deepEqual, equal } from "node:assert"; import test from "node:test"; - -const mockConfig = { - serviceName: "test-service", - serviceId: "test-service-id", - mvtDictionaryId: "test-mvt-dictionary-id", - mvtDictionaryName: "test-mvt-dictionary", - abTestsDictionaryId: "test-ab-tests-dictionary-id", - abTestsDictionaryName: "test-ab-tests-dictionary", -}; - -// Mock environment variables -process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig); -process.env.FASTLY_API_TOKEN = "test-api-token"; - -// Import after mocking -const { calculateUpdates, encodeObject } = await import("./utils.ts"); +import { calculateUpdates, encodeObject } from "./utils.ts"; test("calculateUpdates - creates new items", () => { const currentDictionary: Array<{ item_key: string; item_value: string }> = diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/scripts/build/index.ts index 94851665885..bb26e17e2ab 100644 --- a/ab-testing/scripts/build/index.ts +++ b/ab-testing/scripts/build/index.ts @@ -2,8 +2,13 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { parseArgs } from "node:util"; import { activeABtests } from "../../abTests.ts"; +import { + mvtDictionaryId, + mvtDictionaryName, + serviceId, + serviceName, +} from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; -import { getMVTGroupsFromDictionary } from "../../lib/fastly/dictionary.ts"; import { parseMVTValue, stringifyMVTValue } from "../../lib/fastly-subfield.ts"; import { buildABTestGroupKeyValues } from "./build-ab-tests-dict.ts"; import { calculateAllSpaceUpdates } from "./calculate-mvt-updates.ts"; @@ -31,10 +36,17 @@ if (!flags["mvts"] || !flags["ab-tests"]) { const fastly = new FastlyClient(process.env.FASTLY_API_TOKEN ?? ""); -const mvtGroupsDictionary = await getMVTGroupsFromDictionary(fastly); +const service = await fastly.getService(serviceId, serviceName); + +const mvtGroupsDictionary = await service.getDictionary( + mvtDictionaryId, + mvtDictionaryName, +); + +const mvtGroupsDictionaryItems = await mvtGroupsDictionary.getItems(); const mvtGroups = new Map( - mvtGroupsDictionary.map(({ item_key, item_value }) => { + mvtGroupsDictionaryItems.map(({ item_key, item_value }) => { const testGroups = parseMVTValue(item_value); return [item_key, testGroups]; }), From bc7fe94da5a362a1d12b1e321993b12d14d17512 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 16:56:01 +0000 Subject: [PATCH 12/31] fix dictionary read --- ab-testing/dictionary-deploy-lambda/src/index.ts | 16 +++------------- ab-testing/lib/fastly/client.ts | 2 +- ab-testing/lib/fastly/service.test.ts | 5 +---- ab-testing/lib/fastly/service.ts | 10 ++-------- ab-testing/scripts/build/index.ts | 12 ++---------- 5 files changed, 9 insertions(+), 36 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 2091c76105c..46de316cd0f 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -43,26 +43,16 @@ export const handler: Handler = async ( throw new Error("Fastly API token not found in SSM Parameter Store"); } - const { - serviceId, - serviceName, - abTestsDictionaryId, - abTestsDictionaryName, - mvtDictionaryId, - mvtDictionaryName, - } = await getFastlyConfig(); + const { serviceId, serviceName, abTestsDictionaryName, mvtDictionaryName } = + await getFastlyConfig(); const fastly = new FastlyClient(apiToken); const service = await fastly.getService(serviceId, serviceName); const abTestsDictionary = await service.getDictionary( - abTestsDictionaryId, abTestsDictionaryName, ); - const mvtDictionary = await service.getDictionary( - mvtDictionaryId, - mvtDictionaryName, - ); + const mvtDictionary = await service.getDictionary(mvtDictionaryName); if (event.RequestType === "Create" || event.RequestType === "Update") { try { diff --git a/ab-testing/lib/fastly/client.ts b/ab-testing/lib/fastly/client.ts index 0b4c62057d2..b946434a10a 100644 --- a/ab-testing/lib/fastly/client.ts +++ b/ab-testing/lib/fastly/client.ts @@ -109,7 +109,7 @@ export class FastlyClient { if (serviceConfig.name !== serviceName) { throw new Error( - `Service ID ${serviceId} does not match the expected service name ${serviceName}`, + `Service with ID ${serviceId} does not match the expected service name ${serviceName}`, ); } diff --git a/ab-testing/lib/fastly/service.test.ts b/ab-testing/lib/fastly/service.test.ts index 82d02745456..7b52a5e55d3 100644 --- a/ab-testing/lib/fastly/service.test.ts +++ b/ab-testing/lib/fastly/service.test.ts @@ -32,10 +32,7 @@ describe("FastlyService", async () => { activeVersion: 1, }); - const dictionary = await service.getDictionary( - "dict-123", - "test-dictionary", - ); + const dictionary = await service.getDictionary("test-dictionary"); equal(dictionary instanceof FastlyDictionary, true); equal(dictionary.id, "dict-123"); diff --git a/ab-testing/lib/fastly/service.ts b/ab-testing/lib/fastly/service.ts index 1f774842ede..b41a49f2e69 100644 --- a/ab-testing/lib/fastly/service.ts +++ b/ab-testing/lib/fastly/service.ts @@ -21,19 +21,13 @@ export class FastlyService { this.activeVersion = activeVersion; } - async getDictionary(id: string, name: string): Promise { + async getDictionary(name: string): Promise { const dictionaryConfig = await this.client.getDictionary({ activeVersion: this.activeVersion, - dictionaryName: id, + dictionaryName: name, serviceId: this.id, }); - if (dictionaryConfig.name !== name) { - throw new Error( - `Dictionary name ${dictionaryConfig.name} does not match expected name ${name}`, - ); - } - return new FastlyDictionary(this, dictionaryConfig); } diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/scripts/build/index.ts index bb26e17e2ab..539315dc134 100644 --- a/ab-testing/scripts/build/index.ts +++ b/ab-testing/scripts/build/index.ts @@ -2,12 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { parseArgs } from "node:util"; import { activeABtests } from "../../abTests.ts"; -import { - mvtDictionaryId, - mvtDictionaryName, - serviceId, - serviceName, -} from "../../lib/config.ts"; +import { mvtDictionaryName, serviceId, serviceName } from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; import { parseMVTValue, stringifyMVTValue } from "../../lib/fastly-subfield.ts"; import { buildABTestGroupKeyValues } from "./build-ab-tests-dict.ts"; @@ -38,10 +33,7 @@ const fastly = new FastlyClient(process.env.FASTLY_API_TOKEN ?? ""); const service = await fastly.getService(serviceId, serviceName); -const mvtGroupsDictionary = await service.getDictionary( - mvtDictionaryId, - mvtDictionaryName, -); +const mvtGroupsDictionary = await service.getDictionary(mvtDictionaryName); const mvtGroupsDictionaryItems = await mvtGroupsDictionary.getItems(); From dacfba359ad0f863408b30a68dc9e7957d8aaadb Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 16:59:07 +0000 Subject: [PATCH 13/31] snapshot --- .../dictionaryDeployLambda.test.ts.snap | 108 ++++++++++++++++-- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap index d529f18006f..bfe3a738864 100644 --- a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap @@ -197,6 +197,56 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` ] } ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/CODE/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/CODE/fastly-ab-testing-config" + ] + ] + } } ], "Version": "2012-10-17" @@ -220,10 +270,6 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` }, "Environment": { "Variables": { - "FASTLY_API_TOKEN": "{{resolve:ssm-secure:/ab-testing-deploy/CODE/fastly-api-token}}", - "FASTLY_AB_TESTING_CONFIG": { - "Ref": "FastlyAbTestingConfigParameterParameter" - }, "STAGE": "CODE", "ARTIFACT_BUCKET_NAME": { "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" @@ -490,6 +536,56 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` ] } ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/PROD/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/PROD/fastly-ab-testing-config" + ] + ] + } } ], "Version": "2012-10-17" @@ -513,10 +609,6 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` }, "Environment": { "Variables": { - "FASTLY_API_TOKEN": "{{resolve:ssm-secure:/ab-testing-deploy/PROD/fastly-api-token}}", - "FASTLY_AB_TESTING_CONFIG": { - "Ref": "FastlyAbTestingConfigParameterParameter" - }, "STAGE": "PROD", "ARTIFACT_BUCKET_NAME": { "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" From 0ee512f3e5dd25ced881c08dfac7e84a19ae94cc Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 18:17:41 +0000 Subject: [PATCH 14/31] fix ssm usage --- .../dictionaryDeployLambda.test.ts.snap | 44 ++++++++----------- ab-testing/cdk/lib/dictionaryDeployLambda.ts | 4 +- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap index bfe3a738864..5b2f80740b8 100644 --- a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap @@ -12,10 +12,6 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/dotcom-store.bucket" }, - "FastlyAbTestingConfigParameterParameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/ab-testing-deploy/CODE/fastly-ab-testing-config" - }, "DistributionBucketName": { "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/artifact.bucket", @@ -23,7 +19,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` } }, "Resources": { - "ID5BatonLambdaServiceRole5E3112D1": { + "DictionaryDeployLambdaServiceRoleAD814AB7": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -76,7 +72,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` ] } }, - "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87": { + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -251,15 +247,15 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` ], "Version": "2012-10-17" }, - "PolicyName": "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", "Roles": [ { - "Ref": "ID5BatonLambdaServiceRole5E3112D1" + "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" } ] } }, - "ID5BatonLambda41DA535C": { + "DictionaryDeployLambdaD0FF981C": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { @@ -286,7 +282,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "MemorySize": 256, "Role": { "Fn::GetAtt": [ - "ID5BatonLambdaServiceRole5E3112D1", + "DictionaryDeployLambdaServiceRoleAD814AB7", "Arn" ] }, @@ -316,8 +312,8 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Timeout": 30 }, "DependsOn": [ - "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", - "ID5BatonLambdaServiceRole5E3112D1" + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "DictionaryDeployLambdaServiceRoleAD814AB7" ] }, "InvokeDictionaryDeployLambda": { @@ -325,7 +321,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "ID5BatonLambda41DA535C", + "DictionaryDeployLambdaD0FF981C", "Arn" ] } @@ -351,10 +347,6 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/dotcom-store.bucket" }, - "FastlyAbTestingConfigParameterParameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/ab-testing-deploy/PROD/fastly-ab-testing-config" - }, "DistributionBucketName": { "Type": "AWS::SSM::Parameter::Value", "Default": "/account/services/artifact.bucket", @@ -362,7 +354,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` } }, "Resources": { - "ID5BatonLambdaServiceRole5E3112D1": { + "DictionaryDeployLambdaServiceRoleAD814AB7": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -415,7 +407,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` ] } }, - "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87": { + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -590,15 +582,15 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` ], "Version": "2012-10-17" }, - "PolicyName": "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", + "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", "Roles": [ { - "Ref": "ID5BatonLambdaServiceRole5E3112D1" + "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" } ] } }, - "ID5BatonLambda41DA535C": { + "DictionaryDeployLambdaD0FF981C": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { @@ -625,7 +617,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "MemorySize": 256, "Role": { "Fn::GetAtt": [ - "ID5BatonLambdaServiceRole5E3112D1", + "DictionaryDeployLambdaServiceRoleAD814AB7", "Arn" ] }, @@ -655,8 +647,8 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Timeout": 30 }, "DependsOn": [ - "ID5BatonLambdaServiceRoleDefaultPolicy3736CB87", - "ID5BatonLambdaServiceRole5E3112D1" + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "DictionaryDeployLambdaServiceRoleAD814AB7" ] }, "InvokeDictionaryDeployLambda": { @@ -664,7 +656,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "ID5BatonLambda41DA535C", + "DictionaryDeployLambdaD0FF981C", "Arn" ] } diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.ts b/ab-testing/cdk/lib/dictionaryDeployLambda.ts index 8057538b8b2..e3882b613a3 100644 --- a/ab-testing/cdk/lib/dictionaryDeployLambda.ts +++ b/ab-testing/cdk/lib/dictionaryDeployLambda.ts @@ -36,7 +36,7 @@ export class DictionaryDeployLambda extends GuStack { ); const fastlyConfigParameter = - StringParameter.fromStringParameterAttributes( + StringParameter.fromSecureStringParameterAttributes( this, "FastlyAbTestingConfigParameter", { @@ -44,7 +44,7 @@ export class DictionaryDeployLambda extends GuStack { }, ); - const lambda = new GuLambdaFunction(this, "ID5BatonLambda", { + const lambda = new GuLambdaFunction(this, "DictionaryDeployLambda", { functionName: `${app}-${this.stage}`, fileName: "lambda.zip", handler: "index.handler", From 092f3d8e87afb8dec40221b21e0a520a9c426eee Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 18:29:40 +0000 Subject: [PATCH 15/31] don't try and read fastly from env --- .../dictionary-deploy-lambda/src/deploy.ts | 5 +-- ab-testing/lib/config.ts | 33 ++++++------------- ab-testing/scripts/build/index.ts | 6 ++-- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts index cde9e17f208..416a1218c3a 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -1,9 +1,10 @@ +import { getEnv } from "../../lib/config.ts"; import type { FastlyDictionary } from "../../lib/fastly/dictionary.ts"; import { deployDictionary } from "./deploy-dictionary.ts"; import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; -const ARTIFACT_BUCKET_NAME = process.env.ARTIFACT_BUCKET_NAME ?? ""; -const STAGE = process.env.STAGE ?? "CODE"; +const ARTIFACT_BUCKET_NAME = getEnv("ARTIFACT_BUCKET_NAME"); +const STAGE = getEnv("STAGE"); const CONFIG_PREFIX = `/${STAGE}/config/ab-testing`; diff --git a/ab-testing/lib/config.ts b/ab-testing/lib/config.ts index a80eb5466e3..6a6359ac4b1 100644 --- a/ab-testing/lib/config.ts +++ b/ab-testing/lib/config.ts @@ -17,28 +17,15 @@ const configStruct = object({ abTestsDictionaryName: string(), }); -const config = JSON.parse(getEnv("FASTLY_AB_TESTING_CONFIG")) as unknown; +const getConfigFromEnv = () => { + const config = JSON.parse(getEnv("FASTLY_AB_TESTING_CONFIG")) as unknown; + assert(config, configStruct); -const apiToken = getEnv("FASTLY_API_TOKEN"); - -assert(config, configStruct); - -const { - serviceName, - serviceId, - mvtDictionaryId, - mvtDictionaryName, - abTestsDictionaryId, - abTestsDictionaryName, -} = config; - -export { - apiToken, - serviceName, - serviceId, - mvtDictionaryId, - mvtDictionaryName, - abTestsDictionaryId, - abTestsDictionaryName, - configStruct, + return config; }; +const getApiTokenFromEnv = () => { + const apiToken = getEnv("FASTLY_API_TOKEN"); + return apiToken; +}; + +export { getConfigFromEnv, getApiTokenFromEnv, getEnv, configStruct }; diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/scripts/build/index.ts index 539315dc134..4f4f1800688 100644 --- a/ab-testing/scripts/build/index.ts +++ b/ab-testing/scripts/build/index.ts @@ -2,7 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { parseArgs } from "node:util"; import { activeABtests } from "../../abTests.ts"; -import { mvtDictionaryName, serviceId, serviceName } from "../../lib/config.ts"; +import { getApiTokenFromEnv, getConfigFromEnv } from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; import { parseMVTValue, stringifyMVTValue } from "../../lib/fastly-subfield.ts"; import { buildABTestGroupKeyValues } from "./build-ab-tests-dict.ts"; @@ -29,7 +29,9 @@ if (!flags["mvts"] || !flags["ab-tests"]) { process.exit(1); } -const fastly = new FastlyClient(process.env.FASTLY_API_TOKEN ?? ""); +const { serviceId, serviceName, mvtDictionaryName } = getConfigFromEnv(); + +const fastly = new FastlyClient(getApiTokenFromEnv()); const service = await fastly.getService(serviceId, serviceName); From 90d60ec4477e5e9547850e1a65637d0c4ad83892 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 18:34:12 +0000 Subject: [PATCH 16/31] ensure error response gets sent --- .../dictionary-deploy-lambda/src/index.ts | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 46de316cd0f..7e1d1a6b9b2 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -7,9 +7,9 @@ import { configStruct } from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; import { fetchAndDeployArtifacts } from "./deploy.ts"; -const ssmClient = new SSMClient({ region: "eu-west-1" }); - const getSecureString = async (name: string) => { + const ssmClient = new SSMClient({ region: "eu-west-1" }); + const response = await ssmClient.send( new GetParameterCommand({ Name: name, @@ -35,27 +35,33 @@ export const handler: Handler = async ( event: CloudFormationCustomResourceEvent, context: Context, ): Promise => { - const apiToken = await getSecureString( - `/ab-testing-deploy/${process.env.STAGE}/fastly-api-token`, - ); + try { + const apiToken = await getSecureString( + `/ab-testing-deploy/${process.env.STAGE}/fastly-api-token`, + ); - if (!apiToken) { - throw new Error("Fastly API token not found in SSM Parameter Store"); - } + if (!apiToken) { + throw new Error( + "Fastly API token not found in SSM Parameter Store", + ); + } - const { serviceId, serviceName, abTestsDictionaryName, mvtDictionaryName } = - await getFastlyConfig(); + const { + serviceId, + serviceName, + abTestsDictionaryName, + mvtDictionaryName, + } = await getFastlyConfig(); - const fastly = new FastlyClient(apiToken); - const service = await fastly.getService(serviceId, serviceName); + const fastly = new FastlyClient(apiToken); + const service = await fastly.getService(serviceId, serviceName); - const abTestsDictionary = await service.getDictionary( - abTestsDictionaryName, - ); - const mvtDictionary = await service.getDictionary(mvtDictionaryName); + const abTestsDictionary = await service.getDictionary( + abTestsDictionaryName, + ); + const mvtDictionary = await service.getDictionary(mvtDictionaryName); - if (event.RequestType === "Create" || event.RequestType === "Update") { - try { + if (event.RequestType === "Create" || event.RequestType === "Update") { await fetchAndDeployArtifacts([ { artifact: "ab-test-groups.json", @@ -68,9 +74,9 @@ export const handler: Handler = async ( ]); send(event, context, "SUCCESS"); - } catch (error) { - console.error("Error deploying dictionaries:", error); - send(event, context, "FAILED"); } + } catch (error) { + console.error("Error deploying dictionaries:", error); + send(event, context, "FAILED", { error: (error as Error).message }); } }; From 5c7e200d2a303009360ad3d9a98c20891953b3d5 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 18:50:04 +0000 Subject: [PATCH 17/31] stack/app name --- ab-testing/cdk/bin/cdk.ts | 10 +- .../__snapshots__/abTestingStack.test.ts.snap | 669 ++++++++++++++++++ ...yLambda.test.ts => abTestingStack.test.ts} | 38 +- ...onaryDeployLambda.ts => abTestingStack.ts} | 2 +- .../dictionary-deploy-lambda/src/index.ts | 4 +- 5 files changed, 692 insertions(+), 31 deletions(-) create mode 100644 ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap rename ab-testing/cdk/lib/{dictionaryDeployLambda.test.ts => abTestingStack.test.ts} (61%) rename ab-testing/cdk/lib/{dictionaryDeployLambda.ts => abTestingStack.ts} (97%) diff --git a/ab-testing/cdk/bin/cdk.ts b/ab-testing/cdk/bin/cdk.ts index 58e958a445c..8e41d6395ea 100644 --- a/ab-testing/cdk/bin/cdk.ts +++ b/ab-testing/cdk/bin/cdk.ts @@ -1,13 +1,13 @@ import "source-map-support/register.js"; import { RiffRaffYamlFile } from "@guardian/cdk/lib/riff-raff-yaml-file/index.js"; import { App } from "aws-cdk-lib"; -import { DictionaryDeployLambda } from "../lib/dictionaryDeployLambda.ts"; +import { AbTestingStack } from "../lib/abTestingStack.ts"; const app = new App(); -const appName = "ab-testing-deploy"; +const appName = "ab-testing"; -new DictionaryDeployLambda(app, "DictionaryDeployLambdaCode", { +new AbTestingStack(app, "AbTestingCode", { stack: "frontend", stage: "CODE", env: { @@ -16,7 +16,7 @@ new DictionaryDeployLambda(app, "DictionaryDeployLambdaCode", { app: appName, }); -new DictionaryDeployLambda(app, "DictionaryDeployLambdaProd", { +new AbTestingStack(app, "AbTestingProd", { stack: "frontend", stage: "PROD", env: { @@ -60,7 +60,7 @@ deployments.set("admin/ab-testing", { // We need the test artifacts in place before deploying the lambda that uses them deployments - .get(`cfn-eu-west-1-frontend-dictionary-deploy-lambda`) + .get(`cfn-eu-west-1-frontend-ab-testing-stack`) ?.dependencies?.push("config/ab-testing"); riffRaff.synth(); diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap new file mode 100644 index 00000000000..5b2f80740b8 --- /dev/null +++ b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap @@ -0,0 +1,669 @@ +exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "DictionaryDeployLambdaServiceRoleAD814AB7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ] + } + }, + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/CODE/ab-testing-deploy/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deploy" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deploy/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/CODE/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/CODE/fastly-ab-testing-config" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "Roles": [ + { + "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" + } + ] + } + }, + "DictionaryDeployLambdaD0FF981C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/CODE/ab-testing-deploy/lambda.zip" + }, + "Environment": { + "Variables": { + "STAGE": "CODE", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deploy" + } + }, + "FunctionName": "ab-testing-deploy-CODE", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "DictionaryDeployLambdaServiceRoleAD814AB7", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "DictionaryDeployLambdaServiceRoleAD814AB7" + ] + }, + "InvokeDictionaryDeployLambda": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "DictionaryDeployLambdaD0FF981C", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; + +exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "DictionaryDeployLambdaServiceRoleAD814AB7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ] + } + }, + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/PROD/ab-testing-deploy/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deploy" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deploy/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/PROD/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing-deploy/PROD/fastly-ab-testing-config" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "Roles": [ + { + "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" + } + ] + } + }, + "DictionaryDeployLambdaD0FF981C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/PROD/ab-testing-deploy/lambda.zip" + }, + "Environment": { + "Variables": { + "STAGE": "PROD", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deploy" + } + }, + "FunctionName": "ab-testing-deploy-PROD", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "DictionaryDeployLambdaServiceRoleAD814AB7", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deploy" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", + "DictionaryDeployLambdaServiceRoleAD814AB7" + ] + }, + "InvokeDictionaryDeployLambda": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "DictionaryDeployLambdaD0FF981C", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.test.ts b/ab-testing/cdk/lib/abTestingStack.test.ts similarity index 61% rename from ab-testing/cdk/lib/dictionaryDeployLambda.test.ts rename to ab-testing/cdk/lib/abTestingStack.test.ts index a32400665d6..a70ad5e19ae 100644 --- a/ab-testing/cdk/lib/dictionaryDeployLambda.test.ts +++ b/ab-testing/cdk/lib/abTestingStack.test.ts @@ -3,7 +3,7 @@ import { snapshot } from "node:test"; import { basename } from "path"; import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; import { Template } from "aws-cdk-lib/assertions"; -import { DictionaryDeployLambda } from "./dictionaryDeployLambda.ts"; +import { AbTestingStack } from "./abTestingStack.ts"; snapshot.setResolveSnapshotPath( () => @@ -15,36 +15,28 @@ snapshot.setResolveSnapshotPath( void describe("The ID5 Baton Lambda stack", () => { void it("matches the CODE snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new DictionaryDeployLambda( - app, - "DictionaryDeployLambda", - { - stack: "frontend", - stage: "CODE", - env: { - region: "eu-west-1", - }, - app: "ab-testing-deploy", + const stack = new AbTestingStack(app, "AbTestingStack", { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", }, - ); + app: "ab-testing-deploy", + }); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); }); void it("matches the PROD snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new DictionaryDeployLambda( - app, - "DictionaryDeployLambda", - { - stack: "frontend", - stage: "PROD", - env: { - region: "eu-west-1", - }, - app: "ab-testing-deploy", + const stack = new AbTestingStack(app, "AbTestingStack", { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", }, - ); + app: "ab-testing-deploy", + }); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); }); diff --git a/ab-testing/cdk/lib/dictionaryDeployLambda.ts b/ab-testing/cdk/lib/abTestingStack.ts similarity index 97% rename from ab-testing/cdk/lib/dictionaryDeployLambda.ts rename to ab-testing/cdk/lib/abTestingStack.ts index e3882b613a3..28d9b24a369 100644 --- a/ab-testing/cdk/lib/dictionaryDeployLambda.ts +++ b/ab-testing/cdk/lib/abTestingStack.ts @@ -11,7 +11,7 @@ type Props = GuStackProps & { app: string; }; -export class DictionaryDeployLambda extends GuStack { +export class AbTestingStack extends GuStack { constructor(scope: App, id: string, props: Props) { super(scope, id, props); diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 7e1d1a6b9b2..68079145942 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -21,7 +21,7 @@ const getSecureString = async (name: string) => { const getFastlyConfig = async () => { const stringParam = await getSecureString( - `/ab-testing-deploy/${process.env.STAGE}/fastly-ab-testing-config`, + `/ab-testing/${process.env.STAGE}/fastly-config`, ); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string is invalid JSON too const json = JSON.parse(stringParam || "{}") as unknown; @@ -37,7 +37,7 @@ export const handler: Handler = async ( ): Promise => { try { const apiToken = await getSecureString( - `/ab-testing-deploy/${process.env.STAGE}/fastly-api-token`, + `/ab-testing/${process.env.STAGE}/fastly-api-token`, ); if (!apiToken) { From a712d54e7db6ffbfd8d1f01257ed30f52e1d3d74 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Tue, 18 Nov 2025 18:58:40 +0000 Subject: [PATCH 18/31] don't forget delete requests --- ab-testing/dictionary-deploy-lambda/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 68079145942..5bb417ae0c8 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -73,6 +73,9 @@ export const handler: Handler = async ( }, ]); + send(event, context, "SUCCESS"); + } else { + // For Delete requests, simply respond with SUCCESS send(event, context, "SUCCESS"); } } catch (error) { From 415cb95153aee0031deba91f894a18a71113c2ca Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 10:15:06 +0000 Subject: [PATCH 19/31] renamed stack --- .github/workflows/ab-testing-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index b3bbe2156e1..cee83ad0bed 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -163,7 +163,7 @@ jobs: contentDirectories: | ab-testing-config-artifacts: - ab-testing/dist - ab-testing-deploy: + ab-testing: - ab-testing/dictionary-deploy-lambda/dist/lambda.zip ab-testing-ui-artifact: - ab-testing/frontend/output/ab-tests.html From 73e9f667094638cc457a6187e07b4861f694636e Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 11:04:37 +0000 Subject: [PATCH 20/31] custom implementation of async response logic --- .../__snapshots__/abTestingStack.test.ts.snap | 10 ++-- ab-testing/cdk/lib/abTestingStack.ts | 5 +- .../src/custom-resource-response.ts | 56 +++++++++++++++++++ .../dictionary-deploy-lambda/src/index.ts | 10 ++-- 4 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap index 5b2f80740b8..ca315826dae 100644 --- a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap @@ -239,7 +239,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/ab-testing-deploy/CODE/fastly-ab-testing-config" + ":parameter/ab-testing-deploy/CODE/fastly-config" ] ] } @@ -324,7 +324,8 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "DictionaryDeployLambdaD0FF981C", "Arn" ] - } + }, + "ServiceTimeout": "300" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -574,7 +575,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/ab-testing-deploy/PROD/fastly-ab-testing-config" + ":parameter/ab-testing-deploy/PROD/fastly-config" ] ] } @@ -659,7 +660,8 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "DictionaryDeployLambdaD0FF981C", "Arn" ] - } + }, + "ServiceTimeout": "300" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" diff --git a/ab-testing/cdk/lib/abTestingStack.ts b/ab-testing/cdk/lib/abTestingStack.ts index 28d9b24a369..380a93eb6d3 100644 --- a/ab-testing/cdk/lib/abTestingStack.ts +++ b/ab-testing/cdk/lib/abTestingStack.ts @@ -3,7 +3,7 @@ import { GuStack } from "@guardian/cdk/lib/constructs/core/stack.js"; import { GuLambdaFunction } from "@guardian/cdk/lib/constructs/lambda/index.js"; import { GuS3Bucket } from "@guardian/cdk/lib/constructs/s3/index.js"; import type { App } from "aws-cdk-lib"; -import { CustomResource } from "aws-cdk-lib"; +import { CustomResource, Duration } from "aws-cdk-lib"; import { Runtime } from "aws-cdk-lib/aws-lambda"; import { StringParameter } from "aws-cdk-lib/aws-ssm"; @@ -40,7 +40,7 @@ export class AbTestingStack extends GuStack { this, "FastlyAbTestingConfigParameter", { - parameterName: `/${app}/${this.stage}/fastly-ab-testing-config`, + parameterName: `/${app}/${this.stage}/fastly-config`, }, ); @@ -64,6 +64,7 @@ export class AbTestingStack extends GuStack { // Trigger the Lambda to run upon deployment new CustomResource(this, "InvokeDictionaryDeployLambda", { serviceToken: lambda.functionArn, + serviceTimeout: Duration.minutes(5), }); } } diff --git a/ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts b/ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts new file mode 100644 index 00000000000..c4670723775 --- /dev/null +++ b/ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts @@ -0,0 +1,56 @@ +import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; + +const SUCCESS = "SUCCESS"; +const FAILED = "FAILED"; + +type Status = typeof SUCCESS | typeof FAILED; + +const maskCredentialsAndSignature = (message: string) => { + return message + .replace(/X-Amz-Credential=[^&\s]+/i, "X-Amz-Credential=*****") + .replace(/X-Amz-Signature=[^&\s]+/i, "X-Amz-Signature=*****"); +}; + +// Modernised implementation of cfn-response's send function using fetch +// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html +const send = async ( + event: CloudFormationCustomResourceEvent, + context: Context, + responseStatus: Status, + responseData?: unknown, + physicalResourceId?: string, +) => { + const responseBody = JSON.stringify({ + Status: responseStatus, + Reason: + "See the details in CloudWatch Log Stream: " + + context.logStreamName, + PhysicalResourceId: physicalResourceId ?? context.logStreamName, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: responseData, + }); + + console.log("Response body:\n", responseBody); + console.log("Sending response to:", event.ResponseURL); + + try { + await fetch(event.ResponseURL, { + method: "PUT", + headers: { + "content-type": "", + }, + body: responseBody, + }); + } catch (error) { + console.log( + "send(..) failed executing fetch: " + + maskCredentialsAndSignature(String(error)), + ); + throw error; + } +}; + +export { send, SUCCESS, FAILED }; +export type { Status }; diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 5bb417ae0c8..26e0a05abd3 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -1,10 +1,10 @@ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; import type { Handler } from "aws-cdk-lib/aws-lambda"; import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; -import { send } from "cfn-response"; import { assert } from "superstruct"; import { configStruct } from "../../lib/config.ts"; import { FastlyClient } from "../../lib/fastly/client.ts"; +import { send } from "./custom-resource-response.ts"; import { fetchAndDeployArtifacts } from "./deploy.ts"; const getSecureString = async (name: string) => { @@ -73,13 +73,15 @@ export const handler: Handler = async ( }, ]); - send(event, context, "SUCCESS"); + await send(event, context, "SUCCESS"); } else { // For Delete requests, simply respond with SUCCESS - send(event, context, "SUCCESS"); + await send(event, context, "SUCCESS"); } } catch (error) { console.error("Error deploying dictionaries:", error); - send(event, context, "FAILED", { error: (error as Error).message }); + await send(event, context, "FAILED", { + error: (error as Error).message, + }); } }; From e19a0da9843d1bc56491240df8f118a07fcd80a5 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 11:23:25 +0000 Subject: [PATCH 21/31] simplify delete response --- .../dictionary-deploy-lambda/src/index.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index 26e0a05abd3..e3eefc40e3f 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -35,6 +35,11 @@ export const handler: Handler = async ( event: CloudFormationCustomResourceEvent, context: Context, ): Promise => { + if (event.RequestType === "Delete") { + // No action needed on delete + await send(event, context, "SUCCESS"); + return; + } try { const apiToken = await getSecureString( `/ab-testing/${process.env.STAGE}/fastly-api-token`, @@ -61,23 +66,18 @@ export const handler: Handler = async ( ); const mvtDictionary = await service.getDictionary(mvtDictionaryName); - if (event.RequestType === "Create" || event.RequestType === "Update") { - await fetchAndDeployArtifacts([ - { - artifact: "ab-test-groups.json", - dictionary: abTestsDictionary, - }, - { - artifact: "mvt-groups.json", - dictionary: mvtDictionary, - }, - ]); + await fetchAndDeployArtifacts([ + { + artifact: "ab-test-groups.json", + dictionary: abTestsDictionary, + }, + { + artifact: "mvt-groups.json", + dictionary: mvtDictionary, + }, + ]); - await send(event, context, "SUCCESS"); - } else { - // For Delete requests, simply respond with SUCCESS - await send(event, context, "SUCCESS"); - } + await send(event, context, "SUCCESS"); } catch (error) { console.error("Error deploying dictionaries:", error); await send(event, context, "FAILED", { From 7fd466f0379535ad59fa6ee28292b35fdae5752c Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 11:30:15 +0000 Subject: [PATCH 22/31] artifact naming --- ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap | 4 ++-- ab-testing/cdk/lib/abTestingStack.ts | 1 + ab-testing/dictionary-deploy-lambda/src/index.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap index ca315826dae..5605bff3018 100644 --- a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap @@ -317,7 +317,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` ] }, "InvokeDictionaryDeployLambda": { - "Type": "AWS::CloudFormation::CustomResource", + "Type": "Custom::FastlyEdgeDictionaryDeploy", "Properties": { "ServiceToken": { "Fn::GetAtt": [ @@ -653,7 +653,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` ] }, "InvokeDictionaryDeployLambda": { - "Type": "AWS::CloudFormation::CustomResource", + "Type": "Custom::FastlyEdgeDictionaryDeploy", "Properties": { "ServiceToken": { "Fn::GetAtt": [ diff --git a/ab-testing/cdk/lib/abTestingStack.ts b/ab-testing/cdk/lib/abTestingStack.ts index 380a93eb6d3..e1aaeb03006 100644 --- a/ab-testing/cdk/lib/abTestingStack.ts +++ b/ab-testing/cdk/lib/abTestingStack.ts @@ -65,6 +65,7 @@ export class AbTestingStack extends GuStack { new CustomResource(this, "InvokeDictionaryDeployLambda", { serviceToken: lambda.functionArn, serviceTimeout: Duration.minutes(5), + resourceType: "Custom::FastlyEdgeDictionaryDeploy", }); } } diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/dictionary-deploy-lambda/src/index.ts index e3eefc40e3f..fb66c76cf82 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/dictionary-deploy-lambda/src/index.ts @@ -68,11 +68,11 @@ export const handler: Handler = async ( await fetchAndDeployArtifacts([ { - artifact: "ab-test-groups.json", + artifact: "ab-tests.json", dictionary: abTestsDictionary, }, { - artifact: "mvt-groups.json", + artifact: "mvts.json", dictionary: mvtDictionary, }, ]); From 55ca26a206f95ed2e35605e9af55d4c5e39b77dc Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 12:31:00 +0000 Subject: [PATCH 23/31] more logging --- .../dictionary-deploy-lambda/src/deploy.ts | 11 +++++--- .../src/fetch-artifact.ts | 25 +++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts index 416a1218c3a..fa562d97e59 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -21,14 +21,17 @@ type ArtifactInfo = { export const fetchAndDeployArtifacts = async (deployments: ArtifactInfo[]) => { try { await Promise.all( - deployments.map(({ artifact, dictionary }) => - fetchDictionaryArtifact( + deployments.map(({ artifact, dictionary }) => { + console.log( + `Fetching artifact /${ARTIFACT_BUCKET_NAME}${CONFIG_PREFIX}/${artifact} from S3 and deploying to Fastly dictionary ${dictionary.name}`, + ); + return fetchDictionaryArtifact( ARTIFACT_BUCKET_NAME, `${CONFIG_PREFIX}/${artifact}`, ).then((abTestGroups) => deployDictionary(dictionary, abTestGroups), - ), - ), + ); + }), ); } catch (error) { console.error("Error in fetchAndDeployArtifacts:", error); diff --git a/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts b/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts index ebdf85a109e..2cf9ecffe63 100644 --- a/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts +++ b/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts @@ -25,18 +25,27 @@ const fetchDictionaryArtifact = async ( Bucket, Key, }); - const response = await s3Client.send(getObjectCommand); - if (!response.Body) { - throw new Error("No body found in S3 object response"); - } + try { + const response = await s3Client.send(getObjectCommand); + + if (!response.Body) { + throw new Error("No body found in S3 object response"); + } - const bodyString = await response.Body.transformToString(); - const parsed = JSON.parse(bodyString) as unknown; + const bodyString = await response.Body.transformToString(); + const parsed = JSON.parse(bodyString) as unknown; - const result = create(parsed, array(fastlyKVStruct)); + const result = create(parsed, array(fastlyKVStruct)); - return result; + return result; + } catch (error) { + console.error( + `Failed to fetch artifact from Bucket: ${Bucket}, Key: ${Key}`, + ); + console.error("Error fetching or parsing artifact:", error); + throw error; + } }; export { fetchDictionaryArtifact, type KeyValue }; From 01fbd4211bbdab8acafc4f4a3e8cdea60e8c5a72 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 14:45:42 +0000 Subject: [PATCH 24/31] s3 key format --- ab-testing/dictionary-deploy-lambda/src/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/dictionary-deploy-lambda/src/deploy.ts index fa562d97e59..0a6ed74e8b9 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/dictionary-deploy-lambda/src/deploy.ts @@ -6,7 +6,7 @@ import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; const ARTIFACT_BUCKET_NAME = getEnv("ARTIFACT_BUCKET_NAME"); const STAGE = getEnv("STAGE"); -const CONFIG_PREFIX = `/${STAGE}/config/ab-testing`; +const CONFIG_PREFIX = `${STAGE}/config/ab-testing`; type ArtifactInfo = { artifact: string; From e875c306c2d8821f61605984fd8cc55b44fc4ba2 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 17:22:43 +0000 Subject: [PATCH 25/31] split projects --- .github/workflows/ab-testing-ci.yml | 58 +- .github/workflows/ab-testing-lambda-ci.yml | 87 +++ ab-testing/cdk.context.json | 3 + ab-testing/cdk.json | 1 - .../bin/{cdk.ts => ab-testing-config.cdk.ts} | 24 +- ab-testing/cdk/bin/deployment-lambda.cdk.ts | 21 + .../abTestingConfig.test.ts.snap | 69 ++ .../deploymentLambda.test.ts.snap | 643 ++++++++++++++++++ ...gStack.test.ts => abTestingConfig.test.ts} | 8 +- ab-testing/cdk/lib/abTestingConfig.ts | 24 + ab-testing/cdk/lib/deploymentLambda.test.ts | 41 ++ ...{abTestingStack.ts => deploymentLambda.ts} | 28 +- ab-testing/package.json | 4 +- 13 files changed, 921 insertions(+), 90 deletions(-) create mode 100644 .github/workflows/ab-testing-lambda-ci.yml create mode 100644 ab-testing/cdk.context.json rename ab-testing/cdk/bin/{cdk.ts => ab-testing-config.cdk.ts} (72%) create mode 100644 ab-testing/cdk/bin/deployment-lambda.cdk.ts create mode 100644 ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap create mode 100644 ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap rename ab-testing/cdk/lib/{abTestingStack.test.ts => abTestingConfig.test.ts} (80%) create mode 100644 ab-testing/cdk/lib/abTestingConfig.ts create mode 100644 ab-testing/cdk/lib/deploymentLambda.test.ts rename ab-testing/cdk/lib/{abTestingStack.ts => deploymentLambda.ts} (64%) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index cee83ad0bed..924eea8391e 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -4,21 +4,16 @@ permissions: contents: read on: - pull_request: - paths: - - 'ab-testing/**' - - '.github/workflows/ab-testing-*.yml' push: - branches: - - main paths: - 'ab-testing/**' - - '.github/workflows/ab-testing-*.yml' + - '.github/workflows/ab-testing-ci.yml' + - '!.github/workflows/ab-testing-deployment-lambda/**' jobs: - dictionaries-ci: + config-ci: runs-on: ubuntu-latest - name: Dictionaries CI + name: Config CI defaults: run: working-directory: ab-testing @@ -55,35 +50,6 @@ jobs: name: ab-testing-build path: ab-testing/dist - lambda-ci: - name: Deploy Lambda CI - runs-on: ubuntu-latest - defaults: - run: - working-directory: ab-testing/dictionary-deploy-lambda - permissions: - contents: read - steps: - - uses: actions/checkout@v5 - - - name: Set up Node environment - uses: ./.github/actions/setup-node-env - - - name: Build Lambda - run: pnpm build - - - name: Zip app artifact - run: | - cd dist - zip -r lambda.zip . - zip -j lambda.zip ../package.json - - - name: Save build - uses: actions/upload-artifact@v5 - with: - name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip - ui-ci: name: UI CI runs-on: ubuntu-latest @@ -110,7 +76,7 @@ jobs: riff-raff: runs-on: ubuntu-latest - needs: [dictionaries-ci, ui-ci, lambda-ci] + needs: [dictionaries-ci, ui-ci] permissions: id-token: write contents: read @@ -129,7 +95,7 @@ jobs: - name: Set up Node uses: ./.github/actions/setup-node-env - - name: Fetch build + - name: Fetch config build uses: actions/download-artifact@v6.0.0 with: name: ab-testing-build @@ -141,12 +107,6 @@ jobs: name: ui-build path: ab-testing/frontend/output/ab-tests.html - - name: Fetch Lambda build - uses: actions/download-artifact@v6.0.0 - with: - name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip - - name: CDK Test run: pnpm cdk:test @@ -159,13 +119,11 @@ jobs: roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} githubToken: ${{ secrets.GITHUB_TOKEN }} projectName: dotcom:ab-testing - configPath: ab-testing/cdk.out/riff-raff.yaml + configPath: ab-testing/cdk.out/ab-testing-config/riff-raff.yaml contentDirectories: | ab-testing-config-artifacts: - ab-testing/dist - ab-testing: - - ab-testing/dictionary-deploy-lambda/dist/lambda.zip ab-testing-ui-artifact: - ab-testing/frontend/output/ab-tests.html cdk.out: - - ab-testing/cdk.out + - ab-testing/cdk.out/ab-testing-config diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml new file mode 100644 index 00000000000..967ce7082a4 --- /dev/null +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -0,0 +1,87 @@ +name: 🧪 AB testing + +permissions: + contents: read + +on: + push: + paths: + - 'ab-testing/dictionary-deploy-lambda/**' + - 'ab-testing/lib/**' + - '.github/workflows/ab-testing-lambda-ci.yml' + +jobs: + lambda-ci: + name: Deploy Lambda CI + runs-on: ubuntu-latest + defaults: + run: + working-directory: ab-testing/dictionary-deploy-lambda + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + + - name: Set up Node environment + uses: ./.github/actions/setup-node-env + + - name: Build Lambda + run: pnpm build + + - name: Zip app artifact + run: | + cd dist + zip -r lambda.zip . + zip -j lambda.zip ../package.json + + - name: Save build + uses: actions/upload-artifact@v5 + with: + name: ab-testing-lambda-build + path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip + + riff-raff: + runs-on: ubuntu-latest + needs: [lambda-ci] + permissions: + id-token: write + contents: read + pull-requests: write + defaults: + run: + working-directory: ab-testing + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Node + uses: ./.github/actions/setup-node-env + + - name: Fetch Lambda build + uses: actions/download-artifact@v6.0.0 + with: + name: ab-testing-lambda-build + path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip + + - name: CDK Test + run: pnpm cdk:test + + - name: CDK synth + run: pnpm synth:deployment-lambda + + - name: Riff-Raff Upload + uses: guardian/actions-riff-raff@v4.1.9 + with: + roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} + githubToken: ${{ secrets.GITHUB_TOKEN }} + projectName: dotcom:ab-testing-deployment-lambda + configPath: ab-testing/cdk.out/deployment-lambda/riff-raff.yaml + contentDirectories: | + ab-testing-deployment-lambda: + - ab-testing/dictionary-deploy-lambda/dist/lambda.zip + cdk.out: + - ab-testing/cdk.out/deployment-lambda diff --git a/ab-testing/cdk.context.json b/ab-testing/cdk.context.json new file mode 100644 index 00000000000..b813a35b270 --- /dev/null +++ b/ab-testing/cdk.context.json @@ -0,0 +1,3 @@ +{ + "acknowledged-issue-numbers": [34892] +} diff --git a/ab-testing/cdk.json b/ab-testing/cdk.json index b4b4549bcd6..85ba8a27e69 100644 --- a/ab-testing/cdk.json +++ b/ab-testing/cdk.json @@ -1,5 +1,4 @@ { - "app": "node cdk/bin/cdk.ts", "context": { "aws-cdk:enableDiffNoFail": "true", "@aws-cdk/core:stackRelativeExports": "true" diff --git a/ab-testing/cdk/bin/cdk.ts b/ab-testing/cdk/bin/ab-testing-config.cdk.ts similarity index 72% rename from ab-testing/cdk/bin/cdk.ts rename to ab-testing/cdk/bin/ab-testing-config.cdk.ts index 8e41d6395ea..db70bdf73ed 100644 --- a/ab-testing/cdk/bin/cdk.ts +++ b/ab-testing/cdk/bin/ab-testing-config.cdk.ts @@ -1,28 +1,24 @@ import "source-map-support/register.js"; import { RiffRaffYamlFile } from "@guardian/cdk/lib/riff-raff-yaml-file/index.js"; import { App } from "aws-cdk-lib"; -import { AbTestingStack } from "../lib/abTestingStack.ts"; +import { AbTestingConfig } from "../lib/abTestingConfig.ts"; -const app = new App(); +const app = new App({ outdir: "cdk.out/ab-testing-config" }); -const appName = "ab-testing"; - -new AbTestingStack(app, "AbTestingCode", { +new AbTestingConfig(app, "AbTestingConfigCode", { stack: "frontend", stage: "CODE", env: { region: "eu-west-1", }, - app: appName, }); -new AbTestingStack(app, "AbTestingProd", { +new AbTestingConfig(app, "AbTestingConfigProd", { stack: "frontend", stage: "PROD", env: { region: "eu-west-1", }, - app: appName, }); const riffRaff = new RiffRaffYamlFile(app); @@ -58,9 +54,15 @@ deployments.set("admin/ab-testing", { }, }); +console.log(deployments); + +const cfnDeployment = deployments.get( + `cfn-eu-west-1-frontend-ab-testing-config`, +)!; + // We need the test artifacts in place before deploying the lambda that uses them -deployments - .get(`cfn-eu-west-1-frontend-ab-testing-stack`) - ?.dependencies?.push("config/ab-testing"); +cfnDeployment.dependencies = cfnDeployment.dependencies + ? [...cfnDeployment.dependencies, "config/ab-testing"] + : ["config/ab-testing"]; riffRaff.synth(); diff --git a/ab-testing/cdk/bin/deployment-lambda.cdk.ts b/ab-testing/cdk/bin/deployment-lambda.cdk.ts new file mode 100644 index 00000000000..fd9bb78d27c --- /dev/null +++ b/ab-testing/cdk/bin/deployment-lambda.cdk.ts @@ -0,0 +1,21 @@ +import "source-map-support/register.js"; +import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; +import { DeploymentLambda } from "../lib/deploymentLambda.ts"; + +const app = new GuRoot({ outdir: "cdk.out/deployment-lambda" }); + +new DeploymentLambda(app, "AbTestingDeploymentLambdaCode", { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", + }, +}); + +new DeploymentLambda(app, "AbTestingDeploymentLambdaProd", { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", + }, +}); diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap new file mode 100644 index 00000000000..4d8ce55fcb4 --- /dev/null +++ b/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap @@ -0,0 +1,69 @@ +exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [], + "gu:cdk:version": "62.0.1" + }, + "Resources": { + "InvokeDictionaryDeployLambda": { + "Type": "Custom::FastlyEdgeDictionaryDeploy", + "Properties": { + "ServiceToken": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":function:undefined-CODE" + ] + ] + }, + "ServiceTimeout": "300" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; + +exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [], + "gu:cdk:version": "62.0.1" + }, + "Resources": { + "InvokeDictionaryDeployLambda": { + "Type": "Custom::FastlyEdgeDictionaryDeploy", + "Properties": { + "ServiceToken": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":function:undefined-PROD" + ] + ] + }, + "ServiceTimeout": "300" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} +`; diff --git a/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap new file mode 100644 index 00000000000..f501ed2ffd2 --- /dev/null +++ b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap @@ -0,0 +1,643 @@ +exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "AbTestingDeploymentLambdaServiceRoleC1809305": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deployment-lambda" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ] + } + }, + "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/CODE/ab-testing-deployment-lambda/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deployment-lambda" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/CODE/frontend/ab-testing-deployment-lambda/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing/CODE/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing/CODE/fastly-config" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D", + "Roles": [ + { + "Ref": "AbTestingDeploymentLambdaServiceRoleC1809305" + } + ] + } + }, + "AbTestingDeploymentLambda3C53337B": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/CODE/ab-testing-deployment-lambda/lambda.zip" + }, + "Environment": { + "Variables": { + "STAGE": "CODE", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deployment-lambda" + } + }, + "FunctionName": "ab-testing-deployment-CODE", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "AbTestingDeploymentLambdaServiceRoleC1809305", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deployment-lambda" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "CODE" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D", + "AbTestingDeploymentLambdaServiceRoleC1809305" + ] + } + } +} +`; + +exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuLambdaFunction" + ], + "gu:cdk:version": "62.0.1" + }, + "Parameters": { + "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/dotcom-store.bucket" + }, + "DistributionBucketName": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" + } + }, + "Resources": { + "AbTestingDeploymentLambdaServiceRoleC1809305": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deployment-lambda" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ] + } + }, + "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "DistributionBucketName" + }, + "/frontend/PROD/ab-testing-deployment-lambda/lambda.zip" + ] + ] + } + ] + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deployment-lambda" + ] + ] + } + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/PROD/frontend/ab-testing-deployment-lambda/*" + ] + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing/PROD/fastly-api-token" + ] + ] + } + }, + { + "Action": [ + "ssm:DescribeParameters", + "ssm:GetParameters", + "ssm:GetParameter", + "ssm:GetParameterHistory" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId" + }, + ":parameter/ab-testing/PROD/fastly-config" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D", + "Roles": [ + { + "Ref": "AbTestingDeploymentLambdaServiceRoleC1809305" + } + ] + } + }, + "AbTestingDeploymentLambda3C53337B": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName" + }, + "S3Key": "frontend/PROD/ab-testing-deployment-lambda/lambda.zip" + }, + "Environment": { + "Variables": { + "STAGE": "PROD", + "ARTIFACT_BUCKET_NAME": { + "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "STACK": "frontend", + "APP": "ab-testing-deployment-lambda" + } + }, + "FunctionName": "ab-testing-deployment-PROD", + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON" + }, + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "AbTestingDeploymentLambdaServiceRoleC1809305", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Tags": [ + { + "Key": "App", + "Value": "ab-testing-deployment-lambda" + }, + { + "Key": "gu:cdk:version", + "Value": "62.0.1" + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering" + }, + { + "Key": "Stack", + "Value": "frontend" + }, + { + "Key": "Stage", + "Value": "PROD" + } + ], + "Timeout": 30 + }, + "DependsOn": [ + "AbTestingDeploymentLambdaServiceRoleDefaultPolicyC65D820D", + "AbTestingDeploymentLambdaServiceRoleC1809305" + ] + } + } +} +`; diff --git a/ab-testing/cdk/lib/abTestingStack.test.ts b/ab-testing/cdk/lib/abTestingConfig.test.ts similarity index 80% rename from ab-testing/cdk/lib/abTestingStack.test.ts rename to ab-testing/cdk/lib/abTestingConfig.test.ts index a70ad5e19ae..1b8daf2036a 100644 --- a/ab-testing/cdk/lib/abTestingStack.test.ts +++ b/ab-testing/cdk/lib/abTestingConfig.test.ts @@ -3,7 +3,7 @@ import { snapshot } from "node:test"; import { basename } from "path"; import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; import { Template } from "aws-cdk-lib/assertions"; -import { AbTestingStack } from "./abTestingStack.ts"; +import { AbTestingConfig } from "./abTestingConfig.ts"; snapshot.setResolveSnapshotPath( () => @@ -15,13 +15,12 @@ snapshot.setResolveSnapshotPath( void describe("The ID5 Baton Lambda stack", () => { void it("matches the CODE snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new AbTestingStack(app, "AbTestingStack", { + const stack = new AbTestingConfig(app, "AbTestingConfig", { stack: "frontend", stage: "CODE", env: { region: "eu-west-1", }, - app: "ab-testing-deploy", }); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); @@ -29,13 +28,12 @@ void describe("The ID5 Baton Lambda stack", () => { void it("matches the PROD snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new AbTestingStack(app, "AbTestingStack", { + const stack = new AbTestingConfig(app, "AbTestingConfig", { stack: "frontend", stage: "PROD", env: { region: "eu-west-1", }, - app: "ab-testing-deploy", }); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); diff --git a/ab-testing/cdk/lib/abTestingConfig.ts b/ab-testing/cdk/lib/abTestingConfig.ts new file mode 100644 index 00000000000..0adf8e160a6 --- /dev/null +++ b/ab-testing/cdk/lib/abTestingConfig.ts @@ -0,0 +1,24 @@ +import type { GuStackProps } from "@guardian/cdk/lib/constructs/core/stack.js"; +import { GuStack } from "@guardian/cdk/lib/constructs/core/stack.js"; +import type { App } from "aws-cdk-lib"; +import { CustomResource, Duration } from "aws-cdk-lib"; +import { Function } from "aws-cdk-lib/aws-lambda"; + +export class AbTestingConfig extends GuStack { + constructor(scope: App, id: string, props: GuStackProps) { + super(scope, id, props); + + const lambda = Function.fromFunctionName( + this, + "DeploymentLambdaFunction", + `${props.app}-${this.stage}`, + ); + + // Trigger the Lambda to run upon deployment + new CustomResource(this, "InvokeDictionaryDeployLambda", { + serviceToken: lambda.functionArn, + serviceTimeout: Duration.minutes(5), + resourceType: "Custom::FastlyEdgeDictionaryDeploy", + }); + } +} diff --git a/ab-testing/cdk/lib/deploymentLambda.test.ts b/ab-testing/cdk/lib/deploymentLambda.test.ts new file mode 100644 index 00000000000..3c37245a631 --- /dev/null +++ b/ab-testing/cdk/lib/deploymentLambda.test.ts @@ -0,0 +1,41 @@ +import { describe, it } from "node:test"; +import { snapshot } from "node:test"; +import { basename } from "path"; +import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; +import { Template } from "aws-cdk-lib/assertions"; +import { DeploymentLambda } from "./deploymentLambda.ts"; + +snapshot.setResolveSnapshotPath( + () => + `${import.meta.dirname}/__snapshots__/${basename( + import.meta.filename, + )}.snap`, +); + +void describe("The ID5 Baton Lambda stack", () => { + void it("matches the CODE snapshot", ({ assert }) => { + const app = new GuRoot(); + const stack = new DeploymentLambda(app, "DeploymentLambda", { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", + }, + }); + const template = Template.fromStack(stack); + assert.snapshot(template.toJSON()); + }); + + void it("matches the PROD snapshot", ({ assert }) => { + const app = new GuRoot(); + const stack = new DeploymentLambda(app, "DeploymentLambda", { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", + }, + }); + const template = Template.fromStack(stack); + assert.snapshot(template.toJSON()); + }); +}); diff --git a/ab-testing/cdk/lib/abTestingStack.ts b/ab-testing/cdk/lib/deploymentLambda.ts similarity index 64% rename from ab-testing/cdk/lib/abTestingStack.ts rename to ab-testing/cdk/lib/deploymentLambda.ts index e1aaeb03006..3c509ac2e5c 100644 --- a/ab-testing/cdk/lib/abTestingStack.ts +++ b/ab-testing/cdk/lib/deploymentLambda.ts @@ -3,20 +3,13 @@ import { GuStack } from "@guardian/cdk/lib/constructs/core/stack.js"; import { GuLambdaFunction } from "@guardian/cdk/lib/constructs/lambda/index.js"; import { GuS3Bucket } from "@guardian/cdk/lib/constructs/s3/index.js"; import type { App } from "aws-cdk-lib"; -import { CustomResource, Duration } from "aws-cdk-lib"; import { Runtime } from "aws-cdk-lib/aws-lambda"; import { StringParameter } from "aws-cdk-lib/aws-ssm"; -type Props = GuStackProps & { - app: string; -}; - -export class AbTestingStack extends GuStack { - constructor(scope: App, id: string, props: Props) { +export class DeploymentLambda extends GuStack { + constructor(scope: App, id: string, props: GuStackProps) { super(scope, id, props); - const { app } = props; - const s3Bucket = GuS3Bucket.fromBucketName( this, "DictionaryDeployBucket", @@ -31,7 +24,7 @@ export class AbTestingStack extends GuStack { this, "FastlyApiKeyParameter", { - parameterName: `/${app}/${this.stage}/fastly-api-token`, + parameterName: `/ab-testing/${this.stage}/fastly-api-token`, }, ); @@ -40,15 +33,15 @@ export class AbTestingStack extends GuStack { this, "FastlyAbTestingConfigParameter", { - parameterName: `/${app}/${this.stage}/fastly-config`, + parameterName: `/ab-testing/${this.stage}/fastly-config`, }, ); - const lambda = new GuLambdaFunction(this, "DictionaryDeployLambda", { - functionName: `${app}-${this.stage}`, + const lambda = new GuLambdaFunction(this, "AbTestingDeploymentLambda", { + functionName: `ab-testing-deployment-${this.stage}`, fileName: "lambda.zip", handler: "index.handler", - app, + app: "ab-testing-deployment-lambda", runtime: Runtime.NODEJS_22_X, memorySize: 256, environment: { @@ -60,12 +53,5 @@ export class AbTestingStack extends GuStack { s3Bucket.grantRead(lambda); fastlyApiKeyParameter.grantRead(lambda); fastlyConfigParameter.grantRead(lambda); - - // Trigger the Lambda to run upon deployment - new CustomResource(this, "InvokeDictionaryDeployLambda", { - serviceToken: lambda.functionArn, - serviceTimeout: Duration.minutes(5), - resourceType: "Custom::FastlyEdgeDictionaryDeploy", - }); } } diff --git a/ab-testing/package.json b/ab-testing/package.json index 2a79be3cba2..bb3fbe76bd9 100644 --- a/ab-testing/package.json +++ b/ab-testing/package.json @@ -16,8 +16,8 @@ "prettier:fix": "prettier . --write --cache", "cdk:test": "node --test --test-reporter spec cdk/*/*.test.ts", "cdk:test-update": "node --test --test-reporter spec --test-update-snapshots cdk/*/*.test.ts", - "cdk:synth": "cdk synth --path-metadata false --version-reporting false", - "cdk:diff": "cdk diff --path-metadata false --version-reporting false" + "synth:ab-testing-config": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/ab-testing-config.cdk.ts' --output ./cdk.out/ab-testing-config", + "synth:deployment-lambda": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/deployment-lambda.cdk.ts' --output ./cdk.out/deployment-lambda" }, "dependencies": { "superstruct": "2.0.2" From 1cec5ed57968ede737e68a7f5e00338a16c406a3 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Wed, 19 Nov 2025 17:42:40 +0000 Subject: [PATCH 26/31] naming --- ab-testing/cdk/bin/deployment-lambda.cdk.ts | 6 +++--- ab-testing/cdk/lib/abTestingConfig.ts | 3 ++- ab-testing/cdk/lib/deploymentLambda.ts | 8 +++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ab-testing/cdk/bin/deployment-lambda.cdk.ts b/ab-testing/cdk/bin/deployment-lambda.cdk.ts index fd9bb78d27c..05fa16364b6 100644 --- a/ab-testing/cdk/bin/deployment-lambda.cdk.ts +++ b/ab-testing/cdk/bin/deployment-lambda.cdk.ts @@ -1,10 +1,10 @@ import "source-map-support/register.js"; import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; -import { DeploymentLambda } from "../lib/deploymentLambda.ts"; +import { AbTestingDeploymentLambda } from "../lib/deploymentLambda.ts"; const app = new GuRoot({ outdir: "cdk.out/deployment-lambda" }); -new DeploymentLambda(app, "AbTestingDeploymentLambdaCode", { +new AbTestingDeploymentLambda(app, "AbTestingDeploymentLambdaCode", { stack: "frontend", stage: "CODE", env: { @@ -12,7 +12,7 @@ new DeploymentLambda(app, "AbTestingDeploymentLambdaCode", { }, }); -new DeploymentLambda(app, "AbTestingDeploymentLambdaProd", { +new AbTestingDeploymentLambda(app, "AbTestingDeploymentLambdaProd", { stack: "frontend", stage: "PROD", env: { diff --git a/ab-testing/cdk/lib/abTestingConfig.ts b/ab-testing/cdk/lib/abTestingConfig.ts index 0adf8e160a6..42b0cee0d91 100644 --- a/ab-testing/cdk/lib/abTestingConfig.ts +++ b/ab-testing/cdk/lib/abTestingConfig.ts @@ -3,6 +3,7 @@ import { GuStack } from "@guardian/cdk/lib/constructs/core/stack.js"; import type { App } from "aws-cdk-lib"; import { CustomResource, Duration } from "aws-cdk-lib"; import { Function } from "aws-cdk-lib/aws-lambda"; +import { lambdaFunctionName } from "./deploymentLambda.ts"; export class AbTestingConfig extends GuStack { constructor(scope: App, id: string, props: GuStackProps) { @@ -11,7 +12,7 @@ export class AbTestingConfig extends GuStack { const lambda = Function.fromFunctionName( this, "DeploymentLambdaFunction", - `${props.app}-${this.stage}`, + `${lambdaFunctionName}-${this.stage}`, ); // Trigger the Lambda to run upon deployment diff --git a/ab-testing/cdk/lib/deploymentLambda.ts b/ab-testing/cdk/lib/deploymentLambda.ts index 3c509ac2e5c..8991820422e 100644 --- a/ab-testing/cdk/lib/deploymentLambda.ts +++ b/ab-testing/cdk/lib/deploymentLambda.ts @@ -6,7 +6,9 @@ import type { App } from "aws-cdk-lib"; import { Runtime } from "aws-cdk-lib/aws-lambda"; import { StringParameter } from "aws-cdk-lib/aws-ssm"; -export class DeploymentLambda extends GuStack { +export const lambdaFunctionName = "ab-testing-deployment-lambda"; + +export class AbTestingDeploymentLambda extends GuStack { constructor(scope: App, id: string, props: GuStackProps) { super(scope, id, props); @@ -38,10 +40,10 @@ export class DeploymentLambda extends GuStack { ); const lambda = new GuLambdaFunction(this, "AbTestingDeploymentLambda", { - functionName: `ab-testing-deployment-${this.stage}`, + functionName: `${lambdaFunctionName}-${this.stage}`, fileName: "lambda.zip", handler: "index.handler", - app: "ab-testing-deployment-lambda", + app: `${lambdaFunctionName}-lambda`, runtime: Runtime.NODEJS_22_X, memorySize: 256, environment: { From 2ac5ba65a36ff8076ccd169944474a9529a35a86 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Thu, 20 Nov 2025 09:32:39 +0000 Subject: [PATCH 27/31] cdk location --- .github/workflows/ab-testing-ci.yml | 2 +- .github/workflows/ab-testing-lambda-ci.yml | 2 +- .../abTestingConfig.test.ts.snap | 4 +- .../__snapshots__/abTestingStack.test.ts.snap | 671 ------------------ .../deploymentLambda.test.ts.snap | 32 +- .../dictionaryDeployLambda.test.ts.snap | 669 ----------------- ab-testing/cdk/lib/deploymentLambda.test.ts | 34 +- 7 files changed, 41 insertions(+), 1373 deletions(-) delete mode 100644 ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap delete mode 100644 ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 924eea8391e..f2e6d7d53ce 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -125,5 +125,5 @@ jobs: - ab-testing/dist ab-testing-ui-artifact: - ab-testing/frontend/output/ab-tests.html - cdk.out: + cdk.out/ab-testing-config: - ab-testing/cdk.out/ab-testing-config diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml index 967ce7082a4..92f454af1a1 100644 --- a/.github/workflows/ab-testing-lambda-ci.yml +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -83,5 +83,5 @@ jobs: contentDirectories: | ab-testing-deployment-lambda: - ab-testing/dictionary-deploy-lambda/dist/lambda.zip - cdk.out: + cdk.out/deployment-lambda: - ab-testing/cdk.out/deployment-lambda diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap index 4d8ce55fcb4..088ca4513ec 100644 --- a/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/abTestingConfig.test.ts.snap @@ -20,7 +20,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":function:undefined-CODE" + ":function:ab-testing-deployment-lambda-CODE" ] ] }, @@ -55,7 +55,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":function:undefined-PROD" + ":function:ab-testing-deployment-lambda-PROD" ] ] }, diff --git a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap deleted file mode 100644 index 5605bff3018..00000000000 --- a/ab-testing/cdk/lib/__snapshots__/abTestingStack.test.ts.snap +++ /dev/null @@ -1,671 +0,0 @@ -exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` -{ - "Metadata": { - "gu:cdk:constructs": [ - "GuDistributionBucketParameter", - "GuLambdaFunction" - ], - "gu:cdk:version": "62.0.1" - }, - "Parameters": { - "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/dotcom-store.bucket" - }, - "DistributionBucketName": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/artifact.bucket", - "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" - } - }, - "Resources": { - "DictionaryDeployLambdaServiceRoleAD814AB7": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ], - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "CODE" - } - ] - } - }, - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - }, - "/frontend/CODE/ab-testing-deploy/lambda.zip" - ] - ] - } - ] - }, - { - "Action": "ssm:GetParametersByPath", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/CODE/frontend/ab-testing-deploy" - ] - ] - } - }, - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/CODE/frontend/ab-testing-deploy/*" - ] - ] - } - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "/*" - ] - ] - } - ] - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/CODE/fastly-api-token" - ] - ] - } - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/CODE/fastly-config" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "Roles": [ - { - "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" - } - ] - } - }, - "DictionaryDeployLambdaD0FF981C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "DistributionBucketName" - }, - "S3Key": "frontend/CODE/ab-testing-deploy/lambda.zip" - }, - "Environment": { - "Variables": { - "STAGE": "CODE", - "ARTIFACT_BUCKET_NAME": { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "STACK": "frontend", - "APP": "ab-testing-deploy" - } - }, - "FunctionName": "ab-testing-deploy-CODE", - "Handler": "index.handler", - "LoggingConfig": { - "LogFormat": "JSON" - }, - "MemorySize": 256, - "Role": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaServiceRoleAD814AB7", - "Arn" - ] - }, - "Runtime": "nodejs22.x", - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "CODE" - } - ], - "Timeout": 30 - }, - "DependsOn": [ - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "DictionaryDeployLambdaServiceRoleAD814AB7" - ] - }, - "InvokeDictionaryDeployLambda": { - "Type": "Custom::FastlyEdgeDictionaryDeploy", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaD0FF981C", - "Arn" - ] - }, - "ServiceTimeout": "300" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - } -} -`; - -exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` -{ - "Metadata": { - "gu:cdk:constructs": [ - "GuDistributionBucketParameter", - "GuLambdaFunction" - ], - "gu:cdk:version": "62.0.1" - }, - "Parameters": { - "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/dotcom-store.bucket" - }, - "DistributionBucketName": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/artifact.bucket", - "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" - } - }, - "Resources": { - "DictionaryDeployLambdaServiceRoleAD814AB7": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ], - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "PROD" - } - ] - } - }, - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - }, - "/frontend/PROD/ab-testing-deploy/lambda.zip" - ] - ] - } - ] - }, - { - "Action": "ssm:GetParametersByPath", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/PROD/frontend/ab-testing-deploy" - ] - ] - } - }, - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/PROD/frontend/ab-testing-deploy/*" - ] - ] - } - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "/*" - ] - ] - } - ] - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/PROD/fastly-api-token" - ] - ] - } - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/PROD/fastly-config" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "Roles": [ - { - "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" - } - ] - } - }, - "DictionaryDeployLambdaD0FF981C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "DistributionBucketName" - }, - "S3Key": "frontend/PROD/ab-testing-deploy/lambda.zip" - }, - "Environment": { - "Variables": { - "STAGE": "PROD", - "ARTIFACT_BUCKET_NAME": { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "STACK": "frontend", - "APP": "ab-testing-deploy" - } - }, - "FunctionName": "ab-testing-deploy-PROD", - "Handler": "index.handler", - "LoggingConfig": { - "LogFormat": "JSON" - }, - "MemorySize": 256, - "Role": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaServiceRoleAD814AB7", - "Arn" - ] - }, - "Runtime": "nodejs22.x", - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "PROD" - } - ], - "Timeout": 30 - }, - "DependsOn": [ - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "DictionaryDeployLambdaServiceRoleAD814AB7" - ] - }, - "InvokeDictionaryDeployLambda": { - "Type": "Custom::FastlyEdgeDictionaryDeploy", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaD0FF981C", - "Arn" - ] - }, - "ServiceTimeout": "300" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - } -} -`; diff --git a/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap index f501ed2ffd2..0101f9a527f 100644 --- a/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap @@ -51,7 +51,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda" + "Value": "ab-testing-deployment-lambda-lambda" }, { "Key": "gu:cdk:version", @@ -112,7 +112,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "DistributionBucketName" }, - "/frontend/CODE/ab-testing-deployment-lambda/lambda.zip" + "/frontend/CODE/ab-testing-deployment-lambda-lambda/lambda.zip" ] ] } @@ -129,7 +129,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/CODE/frontend/ab-testing-deployment-lambda" + ":parameter/CODE/frontend/ab-testing-deployment-lambda-lambda" ] ] } @@ -148,7 +148,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/CODE/frontend/ab-testing-deployment-lambda/*" + ":parameter/CODE/frontend/ab-testing-deployment-lambda-lambda/*" ] ] } @@ -262,7 +262,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "S3Bucket": { "Ref": "DistributionBucketName" }, - "S3Key": "frontend/CODE/ab-testing-deployment-lambda/lambda.zip" + "S3Key": "frontend/CODE/ab-testing-deployment-lambda-lambda/lambda.zip" }, "Environment": { "Variables": { @@ -271,10 +271,10 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" }, "STACK": "frontend", - "APP": "ab-testing-deployment-lambda" + "APP": "ab-testing-deployment-lambda-lambda" } }, - "FunctionName": "ab-testing-deployment-CODE", + "FunctionName": "ab-testing-deployment-lambda-CODE", "Handler": "index.handler", "LoggingConfig": { "LogFormat": "JSON" @@ -290,7 +290,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda" + "Value": "ab-testing-deployment-lambda-lambda" }, { "Key": "gu:cdk:version", @@ -373,7 +373,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda" + "Value": "ab-testing-deployment-lambda-lambda" }, { "Key": "gu:cdk:version", @@ -434,7 +434,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "DistributionBucketName" }, - "/frontend/PROD/ab-testing-deployment-lambda/lambda.zip" + "/frontend/PROD/ab-testing-deployment-lambda-lambda/lambda.zip" ] ] } @@ -451,7 +451,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/PROD/frontend/ab-testing-deployment-lambda" + ":parameter/PROD/frontend/ab-testing-deployment-lambda-lambda" ] ] } @@ -470,7 +470,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/PROD/frontend/ab-testing-deployment-lambda/*" + ":parameter/PROD/frontend/ab-testing-deployment-lambda-lambda/*" ] ] } @@ -584,7 +584,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "S3Bucket": { "Ref": "DistributionBucketName" }, - "S3Key": "frontend/PROD/ab-testing-deployment-lambda/lambda.zip" + "S3Key": "frontend/PROD/ab-testing-deployment-lambda-lambda/lambda.zip" }, "Environment": { "Variables": { @@ -593,10 +593,10 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" }, "STACK": "frontend", - "APP": "ab-testing-deployment-lambda" + "APP": "ab-testing-deployment-lambda-lambda" } }, - "FunctionName": "ab-testing-deployment-PROD", + "FunctionName": "ab-testing-deployment-lambda-PROD", "Handler": "index.handler", "LoggingConfig": { "LogFormat": "JSON" @@ -612,7 +612,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda" + "Value": "ab-testing-deployment-lambda-lambda" }, { "Key": "gu:cdk:version", diff --git a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap deleted file mode 100644 index 5b2f80740b8..00000000000 --- a/ab-testing/cdk/lib/__snapshots__/dictionaryDeployLambda.test.ts.snap +++ /dev/null @@ -1,669 +0,0 @@ -exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` -{ - "Metadata": { - "gu:cdk:constructs": [ - "GuDistributionBucketParameter", - "GuLambdaFunction" - ], - "gu:cdk:version": "62.0.1" - }, - "Parameters": { - "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/dotcom-store.bucket" - }, - "DistributionBucketName": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/artifact.bucket", - "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" - } - }, - "Resources": { - "DictionaryDeployLambdaServiceRoleAD814AB7": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ], - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "CODE" - } - ] - } - }, - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - }, - "/frontend/CODE/ab-testing-deploy/lambda.zip" - ] - ] - } - ] - }, - { - "Action": "ssm:GetParametersByPath", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/CODE/frontend/ab-testing-deploy" - ] - ] - } - }, - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/CODE/frontend/ab-testing-deploy/*" - ] - ] - } - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "/*" - ] - ] - } - ] - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/CODE/fastly-api-token" - ] - ] - } - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/CODE/fastly-ab-testing-config" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "Roles": [ - { - "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" - } - ] - } - }, - "DictionaryDeployLambdaD0FF981C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "DistributionBucketName" - }, - "S3Key": "frontend/CODE/ab-testing-deploy/lambda.zip" - }, - "Environment": { - "Variables": { - "STAGE": "CODE", - "ARTIFACT_BUCKET_NAME": { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "STACK": "frontend", - "APP": "ab-testing-deploy" - } - }, - "FunctionName": "ab-testing-deploy-CODE", - "Handler": "index.handler", - "LoggingConfig": { - "LogFormat": "JSON" - }, - "MemorySize": 256, - "Role": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaServiceRoleAD814AB7", - "Arn" - ] - }, - "Runtime": "nodejs22.x", - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "CODE" - } - ], - "Timeout": 30 - }, - "DependsOn": [ - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "DictionaryDeployLambdaServiceRoleAD814AB7" - ] - }, - "InvokeDictionaryDeployLambda": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaD0FF981C", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - } -} -`; - -exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` -{ - "Metadata": { - "gu:cdk:constructs": [ - "GuDistributionBucketParameter", - "GuLambdaFunction" - ], - "gu:cdk:version": "62.0.1" - }, - "Parameters": { - "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/dotcom-store.bucket" - }, - "DistributionBucketName": { - "Type": "AWS::SSM::Parameter::Value", - "Default": "/account/services/artifact.bucket", - "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts" - } - }, - "Resources": { - "DictionaryDeployLambdaServiceRoleAD814AB7": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ], - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "PROD" - } - ] - } - }, - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "DistributionBucketName" - }, - "/frontend/PROD/ab-testing-deploy/lambda.zip" - ] - ] - } - ] - }, - { - "Action": "ssm:GetParametersByPath", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/PROD/frontend/ab-testing-deploy" - ] - ] - } - }, - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/PROD/frontend/ab-testing-deploy/*" - ] - ] - } - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "/*" - ] - ] - } - ] - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/PROD/fastly-api-token" - ] - ] - } - }, - { - "Action": [ - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:GetParameterHistory" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ssm:eu-west-1:", - { - "Ref": "AWS::AccountId" - }, - ":parameter/ab-testing-deploy/PROD/fastly-ab-testing-config" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "Roles": [ - { - "Ref": "DictionaryDeployLambdaServiceRoleAD814AB7" - } - ] - } - }, - "DictionaryDeployLambdaD0FF981C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "DistributionBucketName" - }, - "S3Key": "frontend/PROD/ab-testing-deploy/lambda.zip" - }, - "Environment": { - "Variables": { - "STAGE": "PROD", - "ARTIFACT_BUCKET_NAME": { - "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" - }, - "STACK": "frontend", - "APP": "ab-testing-deploy" - } - }, - "FunctionName": "ab-testing-deploy-PROD", - "Handler": "index.handler", - "LoggingConfig": { - "LogFormat": "JSON" - }, - "MemorySize": 256, - "Role": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaServiceRoleAD814AB7", - "Arn" - ] - }, - "Runtime": "nodejs22.x", - "Tags": [ - { - "Key": "App", - "Value": "ab-testing-deploy" - }, - { - "Key": "gu:cdk:version", - "Value": "62.0.1" - }, - { - "Key": "gu:repo", - "Value": "guardian/dotcom-rendering" - }, - { - "Key": "Stack", - "Value": "frontend" - }, - { - "Key": "Stage", - "Value": "PROD" - } - ], - "Timeout": 30 - }, - "DependsOn": [ - "DictionaryDeployLambdaServiceRoleDefaultPolicy63259B6E", - "DictionaryDeployLambdaServiceRoleAD814AB7" - ] - }, - "InvokeDictionaryDeployLambda": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "DictionaryDeployLambdaD0FF981C", - "Arn" - ] - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - } -} -`; diff --git a/ab-testing/cdk/lib/deploymentLambda.test.ts b/ab-testing/cdk/lib/deploymentLambda.test.ts index 3c37245a631..c3d5bcab5ab 100644 --- a/ab-testing/cdk/lib/deploymentLambda.test.ts +++ b/ab-testing/cdk/lib/deploymentLambda.test.ts @@ -3,7 +3,7 @@ import { snapshot } from "node:test"; import { basename } from "path"; import { GuRoot } from "@guardian/cdk/lib/constructs/root.js"; import { Template } from "aws-cdk-lib/assertions"; -import { DeploymentLambda } from "./deploymentLambda.ts"; +import { AbTestingDeploymentLambda } from "./deploymentLambda.ts"; snapshot.setResolveSnapshotPath( () => @@ -15,26 +15,34 @@ snapshot.setResolveSnapshotPath( void describe("The ID5 Baton Lambda stack", () => { void it("matches the CODE snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new DeploymentLambda(app, "DeploymentLambda", { - stack: "frontend", - stage: "CODE", - env: { - region: "eu-west-1", + const stack = new AbTestingDeploymentLambda( + app, + "AbTestingDeploymentLambda", + { + stack: "frontend", + stage: "CODE", + env: { + region: "eu-west-1", + }, }, - }); + ); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); }); void it("matches the PROD snapshot", ({ assert }) => { const app = new GuRoot(); - const stack = new DeploymentLambda(app, "DeploymentLambda", { - stack: "frontend", - stage: "PROD", - env: { - region: "eu-west-1", + const stack = new AbTestingDeploymentLambda( + app, + "AbTestingDeploymentLambda", + { + stack: "frontend", + stage: "PROD", + env: { + region: "eu-west-1", + }, }, - }); + ); const template = Template.fromStack(stack); assert.snapshot(template.toJSON()); }); From d0f9f5ed105e17070fb3e9176ebefb7aea70ba2d Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Thu, 20 Nov 2025 09:47:43 +0000 Subject: [PATCH 28/31] lint-staged --- .github/workflows/ab-testing-lambda-ci.yml | 1 + .../deploymentLambda.test.ts.snap | 28 +++++++++---------- ab-testing/cdk/lib/deploymentLambda.ts | 2 +- ab-testing/package.json | 9 +++++- package.json | 2 +- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml index 92f454af1a1..0f0b190381f 100644 --- a/.github/workflows/ab-testing-lambda-ci.yml +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -7,6 +7,7 @@ on: push: paths: - 'ab-testing/dictionary-deploy-lambda/**' + - 'ab-testing/cdk/**' - 'ab-testing/lib/**' - '.github/workflows/ab-testing-lambda-ci.yml' diff --git a/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap index 0101f9a527f..72619cbfd9e 100644 --- a/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap +++ b/ab-testing/cdk/lib/__snapshots__/deploymentLambda.test.ts.snap @@ -51,7 +51,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda-lambda" + "Value": "ab-testing-deployment-lambda" }, { "Key": "gu:cdk:version", @@ -112,7 +112,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "DistributionBucketName" }, - "/frontend/CODE/ab-testing-deployment-lambda-lambda/lambda.zip" + "/frontend/CODE/ab-testing-deployment-lambda/lambda.zip" ] ] } @@ -129,7 +129,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/CODE/frontend/ab-testing-deployment-lambda-lambda" + ":parameter/CODE/frontend/ab-testing-deployment-lambda" ] ] } @@ -148,7 +148,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/CODE/frontend/ab-testing-deployment-lambda-lambda/*" + ":parameter/CODE/frontend/ab-testing-deployment-lambda/*" ] ] } @@ -262,7 +262,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "S3Bucket": { "Ref": "DistributionBucketName" }, - "S3Key": "frontend/CODE/ab-testing-deployment-lambda-lambda/lambda.zip" + "S3Key": "frontend/CODE/ab-testing-deployment-lambda/lambda.zip" }, "Environment": { "Variables": { @@ -271,7 +271,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" }, "STACK": "frontend", - "APP": "ab-testing-deployment-lambda-lambda" + "APP": "ab-testing-deployment-lambda" } }, "FunctionName": "ab-testing-deployment-lambda-CODE", @@ -290,7 +290,7 @@ exports[`The ID5 Baton Lambda stack > matches the CODE snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda-lambda" + "Value": "ab-testing-deployment-lambda" }, { "Key": "gu:cdk:version", @@ -373,7 +373,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda-lambda" + "Value": "ab-testing-deployment-lambda" }, { "Key": "gu:cdk:version", @@ -434,7 +434,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "DistributionBucketName" }, - "/frontend/PROD/ab-testing-deployment-lambda-lambda/lambda.zip" + "/frontend/PROD/ab-testing-deployment-lambda/lambda.zip" ] ] } @@ -451,7 +451,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/PROD/frontend/ab-testing-deployment-lambda-lambda" + ":parameter/PROD/frontend/ab-testing-deployment-lambda" ] ] } @@ -470,7 +470,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` { "Ref": "AWS::AccountId" }, - ":parameter/PROD/frontend/ab-testing-deployment-lambda-lambda/*" + ":parameter/PROD/frontend/ab-testing-deployment-lambda/*" ] ] } @@ -584,7 +584,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "S3Bucket": { "Ref": "DistributionBucketName" }, - "S3Key": "frontend/PROD/ab-testing-deployment-lambda-lambda/lambda.zip" + "S3Key": "frontend/PROD/ab-testing-deployment-lambda/lambda.zip" }, "Environment": { "Variables": { @@ -593,7 +593,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Ref": "SsmParameterValueaccountservicesdotcomstorebucketC96584B6F00A464EAD1953AFF4B05118Parameter" }, "STACK": "frontend", - "APP": "ab-testing-deployment-lambda-lambda" + "APP": "ab-testing-deployment-lambda" } }, "FunctionName": "ab-testing-deployment-lambda-PROD", @@ -612,7 +612,7 @@ exports[`The ID5 Baton Lambda stack > matches the PROD snapshot 1`] = ` "Tags": [ { "Key": "App", - "Value": "ab-testing-deployment-lambda-lambda" + "Value": "ab-testing-deployment-lambda" }, { "Key": "gu:cdk:version", diff --git a/ab-testing/cdk/lib/deploymentLambda.ts b/ab-testing/cdk/lib/deploymentLambda.ts index 8991820422e..2acd2449e09 100644 --- a/ab-testing/cdk/lib/deploymentLambda.ts +++ b/ab-testing/cdk/lib/deploymentLambda.ts @@ -43,7 +43,7 @@ export class AbTestingDeploymentLambda extends GuStack { functionName: `${lambdaFunctionName}-${this.stage}`, fileName: "lambda.zip", handler: "index.handler", - app: `${lambdaFunctionName}-lambda`, + app: lambdaFunctionName, runtime: Runtime.NODEJS_22_X, memorySize: 256, environment: { diff --git a/ab-testing/package.json b/ab-testing/package.json index bb3fbe76bd9..0098b90f4da 100644 --- a/ab-testing/package.json +++ b/ab-testing/package.json @@ -17,11 +17,18 @@ "cdk:test": "node --test --test-reporter spec cdk/*/*.test.ts", "cdk:test-update": "node --test --test-reporter spec --test-update-snapshots cdk/*/*.test.ts", "synth:ab-testing-config": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/ab-testing-config.cdk.ts' --output ./cdk.out/ab-testing-config", - "synth:deployment-lambda": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/deployment-lambda.cdk.ts' --output ./cdk.out/deployment-lambda" + "synth:deployment-lambda": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/deployment-lambda.cdk.ts' --output ./cdk.out/deployment-lambda", + "lint-staged": "lint-staged" }, "dependencies": { "superstruct": "2.0.2" }, + "lint-staged": { + "*.ts": [ + "pnpm lint --fix", + "pnpm prettier:fix" + ] + }, "devDependencies": { "@aws-sdk/client-s3": "3.931.0", "@aws-sdk/client-ssm": "3.621.0", diff --git a/package.json b/package.json index b5b91611fab..236778c84a2 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "lint-staged": { "*": "prettier --ignore-unknown --write", - "ab-testing/**/*": "cd ab-testing && deno run validate" + "ab-testing/**/*": "pnpm --filter ab-testing lint-staged" }, "dependencies": { "@guardian/prettier": "5.0.0", From 7f605eaf5fe7ce44897a0ef6ca60125320219dc6 Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Thu, 20 Nov 2025 10:22:25 +0000 Subject: [PATCH 29/31] rename workflow --- .github/workflows/ab-testing-ci.yml | 4 ++-- .github/workflows/ab-testing-lambda-ci.yml | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index f2e6d7d53ce..eb10a2dd1c4 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -76,7 +76,7 @@ jobs: riff-raff: runs-on: ubuntu-latest - needs: [dictionaries-ci, ui-ci] + needs: [config-ci, ui-ci] permissions: id-token: write contents: read @@ -111,7 +111,7 @@ jobs: run: pnpm cdk:test - name: CDK synth - run: pnpm cdk:synth + run: pnpm synth:ab-testing-config - name: Riff-Raff Upload uses: guardian/actions-riff-raff@v4.1.9 diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml index 0f0b190381f..f2839fe4032 100644 --- a/.github/workflows/ab-testing-lambda-ci.yml +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -1,4 +1,4 @@ -name: 🧪 AB testing +name: 🧪 AB testing Lambda permissions: contents: read @@ -12,8 +12,7 @@ on: - '.github/workflows/ab-testing-lambda-ci.yml' jobs: - lambda-ci: - name: Deploy Lambda CI + ci: runs-on: ubuntu-latest defaults: run: @@ -43,7 +42,7 @@ jobs: riff-raff: runs-on: ubuntu-latest - needs: [lambda-ci] + needs: [ci] permissions: id-token: write contents: read From 6349e1e0f5ee2399e13136506f6a28956f2499cf Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Thu, 20 Nov 2025 13:51:23 +0000 Subject: [PATCH 30/31] tidy up --- .github/workflows/ab-testing-ci.yml | 18 +- .github/workflows/ab-testing-lambda-ci.yml | 20 +- ab-testing/README.md | 13 +- ab-testing/cdk.context.json | 3 - ab-testing/cdk/bin/ab-testing-config.cdk.ts | 2 - ab-testing/{ => cdk}/cdk.json | 0 ab-testing/cdk/package.json | 38 ++ ab-testing/cdk/tsconfig.json | 11 + ab-testing/{ => config}/abTests.ts | 0 ab-testing/{ => config}/index.ts | 0 ab-testing/{ => config}/lib/config.ts | 0 ab-testing/{ => config}/lib/constants.ts | 0 .../{ => config}/lib/fastly-subfield.test.ts | 0 .../{ => config}/lib/fastly-subfield.ts | 0 .../{ => config}/lib/fastly/client.test.ts | 0 ab-testing/{ => config}/lib/fastly/client.ts | 0 .../lib/fastly/dictionary.test.ts | 0 .../{ => config}/lib/fastly/dictionary.ts | 0 .../{ => config}/lib/fastly/service.test.ts | 0 ab-testing/{ => config}/lib/fastly/service.ts | 0 .../{ => config}/lib/fastly/utils.test.ts | 0 ab-testing/{ => config}/lib/fastly/utils.ts | 0 ab-testing/{ => config}/lib/types.ts | 0 ab-testing/{ => config}/package.json | 10 +- .../scripts/build/build-ab-tests-dict.ts | 0 .../build/calculate-mvt-updates.test.ts | 0 .../scripts/build/calculate-mvt-updates.ts | 0 .../{ => config}/scripts/build/index.ts | 0 .../build/test-group-mvt-manager.test.ts | 0 .../scripts/build/test-group-mvt-manager.ts | 0 .../scripts/validation/enoughSpace.test.ts | 0 .../scripts/validation/enoughSpace.ts | 0 .../{ => config}/scripts/validation/index.ts | 0 .../validation/limitServerSide.test.ts | 0 .../scripts/validation/limitServerSide.ts | 0 .../scripts/validation/uniqueName.test.ts | 0 .../scripts/validation/uniqueName.ts | 0 .../validation/validExpiration.test.ts | 0 .../scripts/validation/validExpiration.ts | 0 ab-testing/{ => config}/types.ts | 0 .../package.json | 17 +- .../rollup.config.js | 0 .../src/custom-resource-response.ts | 0 .../src/deploy-dictionary.ts | 4 +- .../src/deploy.ts | 4 +- .../src/fetch-artifact.ts | 0 .../src/index.ts | 4 +- ab-testing/deploy-lambda/src/run.ts | 75 +++ .../tsconfig.json | 0 ab-testing/eslint.config.mjs | 6 +- ab-testing/frontend/deno.lock | 615 ------------------ ab-testing/frontend/package.json | 2 +- ab-testing/frontend/src/routes/+page.svelte | 2 +- ab-testing/tsconfig.json | 2 +- dotcom-rendering/package.json | 2 +- .../src/components/Metrics.importable.tsx | 2 +- pnpm-lock.yaml | 125 ++-- pnpm-workspace.yaml | 4 +- 58 files changed, 238 insertions(+), 741 deletions(-) delete mode 100644 ab-testing/cdk.context.json rename ab-testing/{ => cdk}/cdk.json (100%) create mode 100644 ab-testing/cdk/package.json create mode 100644 ab-testing/cdk/tsconfig.json rename ab-testing/{ => config}/abTests.ts (100%) rename ab-testing/{ => config}/index.ts (100%) rename ab-testing/{ => config}/lib/config.ts (100%) rename ab-testing/{ => config}/lib/constants.ts (100%) rename ab-testing/{ => config}/lib/fastly-subfield.test.ts (100%) rename ab-testing/{ => config}/lib/fastly-subfield.ts (100%) rename ab-testing/{ => config}/lib/fastly/client.test.ts (100%) rename ab-testing/{ => config}/lib/fastly/client.ts (100%) rename ab-testing/{ => config}/lib/fastly/dictionary.test.ts (100%) rename ab-testing/{ => config}/lib/fastly/dictionary.ts (100%) rename ab-testing/{ => config}/lib/fastly/service.test.ts (100%) rename ab-testing/{ => config}/lib/fastly/service.ts (100%) rename ab-testing/{ => config}/lib/fastly/utils.test.ts (100%) rename ab-testing/{ => config}/lib/fastly/utils.ts (100%) rename ab-testing/{ => config}/lib/types.ts (100%) rename ab-testing/{ => config}/package.json (57%) rename ab-testing/{ => config}/scripts/build/build-ab-tests-dict.ts (100%) rename ab-testing/{ => config}/scripts/build/calculate-mvt-updates.test.ts (100%) rename ab-testing/{ => config}/scripts/build/calculate-mvt-updates.ts (100%) rename ab-testing/{ => config}/scripts/build/index.ts (100%) rename ab-testing/{ => config}/scripts/build/test-group-mvt-manager.test.ts (100%) rename ab-testing/{ => config}/scripts/build/test-group-mvt-manager.ts (100%) rename ab-testing/{ => config}/scripts/validation/enoughSpace.test.ts (100%) rename ab-testing/{ => config}/scripts/validation/enoughSpace.ts (100%) rename ab-testing/{ => config}/scripts/validation/index.ts (100%) rename ab-testing/{ => config}/scripts/validation/limitServerSide.test.ts (100%) rename ab-testing/{ => config}/scripts/validation/limitServerSide.ts (100%) rename ab-testing/{ => config}/scripts/validation/uniqueName.test.ts (100%) rename ab-testing/{ => config}/scripts/validation/uniqueName.ts (100%) rename ab-testing/{ => config}/scripts/validation/validExpiration.test.ts (100%) rename ab-testing/{ => config}/scripts/validation/validExpiration.ts (100%) rename ab-testing/{ => config}/types.ts (100%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/package.json (74%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/rollup.config.js (100%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/src/custom-resource-response.ts (100%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/src/deploy-dictionary.ts (88%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/src/deploy.ts (90%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/src/fetch-artifact.ts (100%) rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/src/index.ts (94%) create mode 100644 ab-testing/deploy-lambda/src/run.ts rename ab-testing/{dictionary-deploy-lambda => deploy-lambda}/tsconfig.json (100%) delete mode 100644 ab-testing/frontend/deno.lock diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index eb10a2dd1c4..1297b75adf0 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -7,8 +7,8 @@ on: push: paths: - 'ab-testing/**' + - '!ab-testing/deployment-lambda/**' - '.github/workflows/ab-testing-ci.yml' - - '!.github/workflows/ab-testing-deployment-lambda/**' jobs: config-ci: @@ -16,7 +16,7 @@ jobs: name: Config CI defaults: run: - working-directory: ab-testing + working-directory: ab-testing/config env: FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }} FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }} @@ -48,7 +48,7 @@ jobs: - uses: actions/upload-artifact@v5 with: name: ab-testing-build - path: ab-testing/dist + path: ab-testing/config/dist ui-ci: name: UI CI @@ -99,7 +99,7 @@ jobs: uses: actions/download-artifact@v6.0.0 with: name: ab-testing-build - path: ab-testing/dist + path: ab-testing/config/dist - name: Fetch UI build uses: actions/download-artifact@v6.0.0 @@ -108,10 +108,10 @@ jobs: path: ab-testing/frontend/output/ab-tests.html - name: CDK Test - run: pnpm cdk:test + run: pnpm --filter @guardian/ab-testing-cdk test - name: CDK synth - run: pnpm synth:ab-testing-config + run: pnpm --filter @guardian/ab-testing-cdk synth:ab-testing-config - name: Riff-Raff Upload uses: guardian/actions-riff-raff@v4.1.9 @@ -119,11 +119,11 @@ jobs: roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} githubToken: ${{ secrets.GITHUB_TOKEN }} projectName: dotcom:ab-testing - configPath: ab-testing/cdk.out/ab-testing-config/riff-raff.yaml + configPath: ab-testing/cdk/cdk.out/ab-testing-config/riff-raff.yaml contentDirectories: | ab-testing-config-artifacts: - - ab-testing/dist + - ab-testing/config/dist ab-testing-ui-artifact: - ab-testing/frontend/output/ab-tests.html cdk.out/ab-testing-config: - - ab-testing/cdk.out/ab-testing-config + - ab-testing/cdk/cdk.out/ab-testing-config diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml index f2839fe4032..3e7d6951846 100644 --- a/.github/workflows/ab-testing-lambda-ci.yml +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -6,9 +6,9 @@ permissions: on: push: paths: - - 'ab-testing/dictionary-deploy-lambda/**' + - 'ab-testing/deploy-lambda/**' - 'ab-testing/cdk/**' - - 'ab-testing/lib/**' + - 'ab-testing/config/lib/**' - '.github/workflows/ab-testing-lambda-ci.yml' jobs: @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: ab-testing/dictionary-deploy-lambda + working-directory: ab-testing/deploy-lambda permissions: contents: read steps: @@ -38,7 +38,7 @@ jobs: uses: actions/upload-artifact@v5 with: name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip + path: ab-testing/deploy-lambda/dist/lambda.zip riff-raff: runs-on: ubuntu-latest @@ -65,13 +65,13 @@ jobs: uses: actions/download-artifact@v6.0.0 with: name: ab-testing-lambda-build - path: ab-testing/dictionary-deploy-lambda/dist/lambda.zip + path: ab-testing/deploy-lambda/dist/lambda.zip - name: CDK Test - run: pnpm cdk:test + run: pnpm --filter @guardian/ab-testing-cdk test - name: CDK synth - run: pnpm synth:deployment-lambda + run: pnpm --filter @guardian/ab-testing-cdk synth:deployment-lambda - name: Riff-Raff Upload uses: guardian/actions-riff-raff@v4.1.9 @@ -79,9 +79,9 @@ jobs: roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} githubToken: ${{ secrets.GITHUB_TOKEN }} projectName: dotcom:ab-testing-deployment-lambda - configPath: ab-testing/cdk.out/deployment-lambda/riff-raff.yaml + configPath: ab-testing/cd/cdk.out/deployment-lambda/riff-raff.yaml contentDirectories: | ab-testing-deployment-lambda: - - ab-testing/dictionary-deploy-lambda/dist/lambda.zip - cdk.out/deployment-lambda: + - ab-testing/deploy-lambda/dist/lambda.zip + cdk.out/cdk/deployment-lambda: - ab-testing/cdk.out/deployment-lambda diff --git a/ab-testing/README.md b/ab-testing/README.md index 4797763ef19..650e8f61d64 100644 --- a/ab-testing/README.md +++ b/ab-testing/README.md @@ -2,16 +2,23 @@ This directory contains the code and configuration for the AB testing framework used on theguardian.com. If you're looking to set up a new server or client side AB test using the new framework then please visit the docs [here](https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/docs/development/ab-testing-in-dcr.md#beta-ab-test-framework). +### Structure + +- `config/` - Contains the configuration and scripts for building and validating AB tests. +- `frontend/` - Contains the source code for the admin interface that shows current test state, viewable from https://frontend.gutools.co.uk/analytics/ab-testing (or https://frontend.code.dev-gutools.co.uk/analytics/ab-testing for CODE) +- `deploy-lambda/` - Contains the code for the lambda function that deploys the AB test state to Fastly edge-dictionaries. +- `cdk/` - Contains the AWS CDK code for deploying the test configuration, UI and lambda function. + ## How it works -The AB testing framework uses Deno to run scripts that validate and deploy the tests. The `deno.json` file contains the tasks that can be run, such as `validate`, `deploy`, and `build`. +The AB testing framework runs scripts that validate, build and deploy the tests. -Which tests using which mvt ids is computed by the `build` task, which generates the `dist/mvts.json` file. This file contains the mapping of AB tests to MVT IDs. +Which tests using which mvt ids is computed by the `build` task, which generates the `config/dist/mvts.json` file. This file contains the mapping of AB tests to MVT IDs. The algorithm allocates tests available MVT IDs based on the audience size and space. Allocation is deterministic, so the same test (with the same name) will always be assigned the same MVT ID as long as the test hasn't been removed, allowing for consistent user experience when tests are added/updated/removed. This also means that new/update tests will not use contiguous MVT IDs, but will instead use whichever MVT IDs are available. However, the allocation is completely separate for each audience space, so if you have a test in space `A` and move it to space `B`, it will be allocated different MVT IDs. -The state of the AB tests is stored in Fastly dictionaries, which are updated when the `deploy` task is run. Logic in fastly VCL will then use these dictionaries to determine which users are in which test groups and set appropriate headers and/or cookies. +The state of the AB tests is uploaded to Fastly edge-dictionaries, where our fastly service can read the test definitions and MVT ID mappings to determine which test groups a user should see. See the [fastly-edge-cache documentation](https://github.com/guardian/fastly-edge-cache/blob/main/theguardiancom/docs/ab-testing.md) for even more details. diff --git a/ab-testing/cdk.context.json b/ab-testing/cdk.context.json deleted file mode 100644 index b813a35b270..00000000000 --- a/ab-testing/cdk.context.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "acknowledged-issue-numbers": [34892] -} diff --git a/ab-testing/cdk/bin/ab-testing-config.cdk.ts b/ab-testing/cdk/bin/ab-testing-config.cdk.ts index db70bdf73ed..6a186cc7982 100644 --- a/ab-testing/cdk/bin/ab-testing-config.cdk.ts +++ b/ab-testing/cdk/bin/ab-testing-config.cdk.ts @@ -54,8 +54,6 @@ deployments.set("admin/ab-testing", { }, }); -console.log(deployments); - const cfnDeployment = deployments.get( `cfn-eu-west-1-frontend-ab-testing-config`, )!; diff --git a/ab-testing/cdk.json b/ab-testing/cdk/cdk.json similarity index 100% rename from ab-testing/cdk.json rename to ab-testing/cdk/cdk.json diff --git a/ab-testing/cdk/package.json b/ab-testing/cdk/package.json new file mode 100644 index 00000000000..1e95063fb31 --- /dev/null +++ b/ab-testing/cdk/package.json @@ -0,0 +1,38 @@ +{ + "name": "@guardian/ab-testing-cdk", + "version": "1.0.0", + "description": "CDK stacks for AB testing infrastructure", + "main": "config/index.ts", + "type": "module", + "scripts": { + "test": "node --test --test-reporter spec cdk/*/*.test.ts", + "test-update": "node --test --test-reporter spec --test-update-snapshots cdk/*/*.test.ts", + "synth:ab-testing-config": "cdk synth --path-metadata false --version-reporting false --app 'node bin/ab-testing-config.cdk.ts' --output ./cdk.out/ab-testing-config", + "synth:deployment-lambda": "cdk synth --path-metadata false --version-reporting false --app 'node bin/deployment-lambda.cdk.ts' --output ./cdk.out/deployment-lambda", + "lint": "eslint .", + "prettier:check": "prettier . --check --cache", + "prettier:fix": "prettier . --write --cache", + "lint-staged": "lint-staged" + }, + "dependencies": {}, + "lint-staged": { + "*.ts": [ + "pnpm lint --fix", + "pnpm prettier:fix" + ] + }, + "devDependencies": { + "@aws-sdk/client-s3": "3.931.0", + "@aws-sdk/client-ssm": "3.621.0", + "@guardian/cdk": "62.0.1", + "@guardian/eslint-config": "12.0.1", + "@guardian/tsconfig": "1.0.1", + "@types/node": "22.17.0", + "aws-cdk": "2.1030.0", + "aws-cdk-lib": "2.220.0", + "eslint": "9.39.1", + "prettier": "3.0.3", + "source-map-support": "0.5.21", + "typescript": "5.9.3" + } +} diff --git a/ab-testing/cdk/tsconfig.json b/ab-testing/cdk/tsconfig.json new file mode 100644 index 00000000000..3a84c85996f --- /dev/null +++ b/ab-testing/cdk/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@guardian/tsconfig/tsconfig.json", + "compilerOptions": { + "noEmit": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "moduleResolution": "nodenext", + "module": "nodenext" + }, + "exclude": ["node_modules"] +} diff --git a/ab-testing/abTests.ts b/ab-testing/config/abTests.ts similarity index 100% rename from ab-testing/abTests.ts rename to ab-testing/config/abTests.ts diff --git a/ab-testing/index.ts b/ab-testing/config/index.ts similarity index 100% rename from ab-testing/index.ts rename to ab-testing/config/index.ts diff --git a/ab-testing/lib/config.ts b/ab-testing/config/lib/config.ts similarity index 100% rename from ab-testing/lib/config.ts rename to ab-testing/config/lib/config.ts diff --git a/ab-testing/lib/constants.ts b/ab-testing/config/lib/constants.ts similarity index 100% rename from ab-testing/lib/constants.ts rename to ab-testing/config/lib/constants.ts diff --git a/ab-testing/lib/fastly-subfield.test.ts b/ab-testing/config/lib/fastly-subfield.test.ts similarity index 100% rename from ab-testing/lib/fastly-subfield.test.ts rename to ab-testing/config/lib/fastly-subfield.test.ts diff --git a/ab-testing/lib/fastly-subfield.ts b/ab-testing/config/lib/fastly-subfield.ts similarity index 100% rename from ab-testing/lib/fastly-subfield.ts rename to ab-testing/config/lib/fastly-subfield.ts diff --git a/ab-testing/lib/fastly/client.test.ts b/ab-testing/config/lib/fastly/client.test.ts similarity index 100% rename from ab-testing/lib/fastly/client.test.ts rename to ab-testing/config/lib/fastly/client.test.ts diff --git a/ab-testing/lib/fastly/client.ts b/ab-testing/config/lib/fastly/client.ts similarity index 100% rename from ab-testing/lib/fastly/client.ts rename to ab-testing/config/lib/fastly/client.ts diff --git a/ab-testing/lib/fastly/dictionary.test.ts b/ab-testing/config/lib/fastly/dictionary.test.ts similarity index 100% rename from ab-testing/lib/fastly/dictionary.test.ts rename to ab-testing/config/lib/fastly/dictionary.test.ts diff --git a/ab-testing/lib/fastly/dictionary.ts b/ab-testing/config/lib/fastly/dictionary.ts similarity index 100% rename from ab-testing/lib/fastly/dictionary.ts rename to ab-testing/config/lib/fastly/dictionary.ts diff --git a/ab-testing/lib/fastly/service.test.ts b/ab-testing/config/lib/fastly/service.test.ts similarity index 100% rename from ab-testing/lib/fastly/service.test.ts rename to ab-testing/config/lib/fastly/service.test.ts diff --git a/ab-testing/lib/fastly/service.ts b/ab-testing/config/lib/fastly/service.ts similarity index 100% rename from ab-testing/lib/fastly/service.ts rename to ab-testing/config/lib/fastly/service.ts diff --git a/ab-testing/lib/fastly/utils.test.ts b/ab-testing/config/lib/fastly/utils.test.ts similarity index 100% rename from ab-testing/lib/fastly/utils.test.ts rename to ab-testing/config/lib/fastly/utils.test.ts diff --git a/ab-testing/lib/fastly/utils.ts b/ab-testing/config/lib/fastly/utils.ts similarity index 100% rename from ab-testing/lib/fastly/utils.ts rename to ab-testing/config/lib/fastly/utils.ts diff --git a/ab-testing/lib/types.ts b/ab-testing/config/lib/types.ts similarity index 100% rename from ab-testing/lib/types.ts rename to ab-testing/config/lib/types.ts diff --git a/ab-testing/package.json b/ab-testing/config/package.json similarity index 57% rename from ab-testing/package.json rename to ab-testing/config/package.json index 0098b90f4da..b7ad40564f3 100644 --- a/ab-testing/package.json +++ b/ab-testing/config/package.json @@ -1,5 +1,5 @@ { - "name": "@guardian/ab-testing", + "name": "@guardian/ab-testing-config", "version": "1.0.0", "description": "A/B test definitions and configuration", "main": "index.ts", @@ -8,16 +8,10 @@ "validate": "node scripts/validation/index.ts", "deploy": "node scripts/deploy/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json", "build": "node scripts/build/index.ts --mvts=dist/mvts.json --ab-tests=dist/ab-tests.json", - "build-ui": "pnpm --filter @guardian/ab-testing-frontend run build", - "lambda:build": "pnpm --filter @guardian/ab-testing-lambda run build", - "test": "node --test --test-reporter spec scripts/**/*.test.ts lib/**/*.test.ts", + "test": "node --test --test-reporter spec ./**/*.test.ts", "lint": "eslint .", "prettier:check": "prettier . --check --cache", "prettier:fix": "prettier . --write --cache", - "cdk:test": "node --test --test-reporter spec cdk/*/*.test.ts", - "cdk:test-update": "node --test --test-reporter spec --test-update-snapshots cdk/*/*.test.ts", - "synth:ab-testing-config": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/ab-testing-config.cdk.ts' --output ./cdk.out/ab-testing-config", - "synth:deployment-lambda": "cdk synth --path-metadata false --version-reporting false --app 'node cdk/bin/deployment-lambda.cdk.ts' --output ./cdk.out/deployment-lambda", "lint-staged": "lint-staged" }, "dependencies": { diff --git a/ab-testing/scripts/build/build-ab-tests-dict.ts b/ab-testing/config/scripts/build/build-ab-tests-dict.ts similarity index 100% rename from ab-testing/scripts/build/build-ab-tests-dict.ts rename to ab-testing/config/scripts/build/build-ab-tests-dict.ts diff --git a/ab-testing/scripts/build/calculate-mvt-updates.test.ts b/ab-testing/config/scripts/build/calculate-mvt-updates.test.ts similarity index 100% rename from ab-testing/scripts/build/calculate-mvt-updates.test.ts rename to ab-testing/config/scripts/build/calculate-mvt-updates.test.ts diff --git a/ab-testing/scripts/build/calculate-mvt-updates.ts b/ab-testing/config/scripts/build/calculate-mvt-updates.ts similarity index 100% rename from ab-testing/scripts/build/calculate-mvt-updates.ts rename to ab-testing/config/scripts/build/calculate-mvt-updates.ts diff --git a/ab-testing/scripts/build/index.ts b/ab-testing/config/scripts/build/index.ts similarity index 100% rename from ab-testing/scripts/build/index.ts rename to ab-testing/config/scripts/build/index.ts diff --git a/ab-testing/scripts/build/test-group-mvt-manager.test.ts b/ab-testing/config/scripts/build/test-group-mvt-manager.test.ts similarity index 100% rename from ab-testing/scripts/build/test-group-mvt-manager.test.ts rename to ab-testing/config/scripts/build/test-group-mvt-manager.test.ts diff --git a/ab-testing/scripts/build/test-group-mvt-manager.ts b/ab-testing/config/scripts/build/test-group-mvt-manager.ts similarity index 100% rename from ab-testing/scripts/build/test-group-mvt-manager.ts rename to ab-testing/config/scripts/build/test-group-mvt-manager.ts diff --git a/ab-testing/scripts/validation/enoughSpace.test.ts b/ab-testing/config/scripts/validation/enoughSpace.test.ts similarity index 100% rename from ab-testing/scripts/validation/enoughSpace.test.ts rename to ab-testing/config/scripts/validation/enoughSpace.test.ts diff --git a/ab-testing/scripts/validation/enoughSpace.ts b/ab-testing/config/scripts/validation/enoughSpace.ts similarity index 100% rename from ab-testing/scripts/validation/enoughSpace.ts rename to ab-testing/config/scripts/validation/enoughSpace.ts diff --git a/ab-testing/scripts/validation/index.ts b/ab-testing/config/scripts/validation/index.ts similarity index 100% rename from ab-testing/scripts/validation/index.ts rename to ab-testing/config/scripts/validation/index.ts diff --git a/ab-testing/scripts/validation/limitServerSide.test.ts b/ab-testing/config/scripts/validation/limitServerSide.test.ts similarity index 100% rename from ab-testing/scripts/validation/limitServerSide.test.ts rename to ab-testing/config/scripts/validation/limitServerSide.test.ts diff --git a/ab-testing/scripts/validation/limitServerSide.ts b/ab-testing/config/scripts/validation/limitServerSide.ts similarity index 100% rename from ab-testing/scripts/validation/limitServerSide.ts rename to ab-testing/config/scripts/validation/limitServerSide.ts diff --git a/ab-testing/scripts/validation/uniqueName.test.ts b/ab-testing/config/scripts/validation/uniqueName.test.ts similarity index 100% rename from ab-testing/scripts/validation/uniqueName.test.ts rename to ab-testing/config/scripts/validation/uniqueName.test.ts diff --git a/ab-testing/scripts/validation/uniqueName.ts b/ab-testing/config/scripts/validation/uniqueName.ts similarity index 100% rename from ab-testing/scripts/validation/uniqueName.ts rename to ab-testing/config/scripts/validation/uniqueName.ts diff --git a/ab-testing/scripts/validation/validExpiration.test.ts b/ab-testing/config/scripts/validation/validExpiration.test.ts similarity index 100% rename from ab-testing/scripts/validation/validExpiration.test.ts rename to ab-testing/config/scripts/validation/validExpiration.test.ts diff --git a/ab-testing/scripts/validation/validExpiration.ts b/ab-testing/config/scripts/validation/validExpiration.ts similarity index 100% rename from ab-testing/scripts/validation/validExpiration.ts rename to ab-testing/config/scripts/validation/validExpiration.ts diff --git a/ab-testing/types.ts b/ab-testing/config/types.ts similarity index 100% rename from ab-testing/types.ts rename to ab-testing/config/types.ts diff --git a/ab-testing/dictionary-deploy-lambda/package.json b/ab-testing/deploy-lambda/package.json similarity index 74% rename from ab-testing/dictionary-deploy-lambda/package.json rename to ab-testing/deploy-lambda/package.json index d5cc1cc7152..9062e6eac86 100644 --- a/ab-testing/dictionary-deploy-lambda/package.json +++ b/ab-testing/deploy-lambda/package.json @@ -1,7 +1,7 @@ { - "name": "@guardian/dictionary-deploy-lambda", + "name": "@guardian/ab-testing-deploy-lambda", "version": "1.0.0", - "description": "A/B test definitions and configuration", + "description": "Lambda that deploys AB test configuration to Fastly edge-dictionaries", "main": "index.ts", "type": "module", "scripts": { @@ -12,28 +12,25 @@ "prettier:fix": "prettier . --write --cache" }, "dependencies": { - "@rollup/plugin-json": "6.1.0", - "cfn-response": "1.0.1", - "esbuild": "0.27.0", - "rollup-plugin-esbuild": "6.2.1", + "@aws-sdk/client-s3": "3.931.0", "superstruct": "2.0.2" }, "devDependencies": { - "@aws-sdk/client-s3": "3.931.0", "@guardian/cdk": "62.0.1", "@guardian/eslint-config": "12.0.1", "@guardian/tsconfig": "1.0.1", "@rollup/plugin-commonjs": "29.0.0", "@rollup/plugin-node-resolve": "16.0.3", - "@rollup/plugin-typescript": "12.3.0", + "@rollup/plugin-json": "6.1.0", "@types/aws-lambda": "8.10.158", - "@types/cfn-response": "1.0.8", "@types/node": "22.17.0", "aws-cdk-lib": "2.220.0", "aws-lambda": "1.0.7", "eslint": "9.39.1", "prettier": "3.0.3", "rollup": "4.53.2", - "typescript": "5.9.3" + "typescript": "5.9.3", + "esbuild": "0.27.0", + "rollup-plugin-esbuild": "6.2.1" } } diff --git a/ab-testing/dictionary-deploy-lambda/rollup.config.js b/ab-testing/deploy-lambda/rollup.config.js similarity index 100% rename from ab-testing/dictionary-deploy-lambda/rollup.config.js rename to ab-testing/deploy-lambda/rollup.config.js diff --git a/ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts b/ab-testing/deploy-lambda/src/custom-resource-response.ts similarity index 100% rename from ab-testing/dictionary-deploy-lambda/src/custom-resource-response.ts rename to ab-testing/deploy-lambda/src/custom-resource-response.ts diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts b/ab-testing/deploy-lambda/src/deploy-dictionary.ts similarity index 88% rename from ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts rename to ab-testing/deploy-lambda/src/deploy-dictionary.ts index 3e3a8b3f090..a90f13d96e1 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy-dictionary.ts +++ b/ab-testing/deploy-lambda/src/deploy-dictionary.ts @@ -1,5 +1,5 @@ -import type { FastlyDictionary } from "../../lib/fastly/dictionary.ts"; -import { calculateUpdates } from "../../lib/fastly/utils.ts"; +import type { FastlyDictionary } from "../../config/lib/fastly/dictionary.ts"; +import { calculateUpdates } from "../../config/lib/fastly/utils.ts"; import type { KeyValue } from "./fetch-artifact.ts"; /** diff --git a/ab-testing/dictionary-deploy-lambda/src/deploy.ts b/ab-testing/deploy-lambda/src/deploy.ts similarity index 90% rename from ab-testing/dictionary-deploy-lambda/src/deploy.ts rename to ab-testing/deploy-lambda/src/deploy.ts index 0a6ed74e8b9..16300ed2a22 100644 --- a/ab-testing/dictionary-deploy-lambda/src/deploy.ts +++ b/ab-testing/deploy-lambda/src/deploy.ts @@ -1,5 +1,5 @@ -import { getEnv } from "../../lib/config.ts"; -import type { FastlyDictionary } from "../../lib/fastly/dictionary.ts"; +import { getEnv } from "../../config/lib/config.ts"; +import type { FastlyDictionary } from "../../config/lib/fastly/dictionary.ts"; import { deployDictionary } from "./deploy-dictionary.ts"; import { fetchDictionaryArtifact } from "./fetch-artifact.ts"; diff --git a/ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts b/ab-testing/deploy-lambda/src/fetch-artifact.ts similarity index 100% rename from ab-testing/dictionary-deploy-lambda/src/fetch-artifact.ts rename to ab-testing/deploy-lambda/src/fetch-artifact.ts diff --git a/ab-testing/dictionary-deploy-lambda/src/index.ts b/ab-testing/deploy-lambda/src/index.ts similarity index 94% rename from ab-testing/dictionary-deploy-lambda/src/index.ts rename to ab-testing/deploy-lambda/src/index.ts index fb66c76cf82..260ce559615 100644 --- a/ab-testing/dictionary-deploy-lambda/src/index.ts +++ b/ab-testing/deploy-lambda/src/index.ts @@ -2,8 +2,8 @@ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; import type { Handler } from "aws-cdk-lib/aws-lambda"; import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; import { assert } from "superstruct"; -import { configStruct } from "../../lib/config.ts"; -import { FastlyClient } from "../../lib/fastly/client.ts"; +import { configStruct } from "../../config/lib/config.ts"; +import { FastlyClient } from "../../config/lib/fastly/client.ts"; import { send } from "./custom-resource-response.ts"; import { fetchAndDeployArtifacts } from "./deploy.ts"; diff --git a/ab-testing/deploy-lambda/src/run.ts b/ab-testing/deploy-lambda/src/run.ts new file mode 100644 index 00000000000..bb58df52e68 --- /dev/null +++ b/ab-testing/deploy-lambda/src/run.ts @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/** + * Run the lambda handler locally using frontend AWS account credentials + * + * Usage: + * STAGE=CODE pnpm tsx src/run.ts + * STAGE=PROD pnpm tsx src/run.ts + * + * Prerequisites: + * - AWS credentials for the frontend account must be configured + * - Use `aws-vault exec frontend -- pnpm tsx src/run.ts` if using aws-vault + */ + +import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda"; +import { handler } from "./index.ts"; + +const stage = process.env.STAGE ?? "CODE"; + +// Create a mock CloudFormation event +const mockEvent: CloudFormationCustomResourceEvent = { + RequestType: "Create", + ServiceToken: + "arn:aws:lambda:eu-west-1:123456789012:function:mock-function", + ResponseURL: + "https://cloudformation-custom-resource-response.s3.amazonaws.com/mock-url", + StackId: + "arn:aws:cloudformation:eu-west-1:123456789012:stack/mock-stack/mock-id", + RequestId: "mock-request-id", + LogicalResourceId: "MockResource", + ResourceType: "Custom::DictionaryDeployer", + ResourceProperties: { + ServiceToken: + "arn:aws:lambda:eu-west-1:123456789012:function:mock-function", + }, +}; + +// Create a mock Lambda context +const mockContext: Context = { + callbackWaitsForEmptyEventLoop: false, + functionName: "ab-testing-deploy-lambda-local", + functionVersion: "$LATEST", + invokedFunctionArn: + "arn:aws:lambda:eu-west-1:123456789012:function:mock-function", + memoryLimitInMB: "512", + awsRequestId: "mock-request-id", + logGroupName: "/aws/lambda/ab-testing-deploy-lambda-local", + logStreamName: "2025/11/19/[$LATEST]mock-stream", + getRemainingTimeInMillis: () => 300000, + done: () => {}, + fail: () => {}, + succeed: () => {}, +}; + +console.log(`Running lambda handler locally with STAGE=${stage}`); +console.log(`Using artifact bucket: ${process.env.ARTIFACT_BUCKET_NAME}`); +console.log(""); + +// Run the handler +// Cast to unknown then to the correct function type to work around typing issues +const runHandler = handler as unknown as ( + event: CloudFormationCustomResourceEvent, + context: Context, + callback: () => void, +) => Promise; + +void (async () => { + try { + await runHandler(mockEvent, mockContext, () => {}); + console.log("\n✅ Lambda handler completed successfully"); + process.exit(0); + } catch (error) { + console.error("\n❌ Lambda handler failed:", error); + process.exit(1); + } +})(); diff --git a/ab-testing/dictionary-deploy-lambda/tsconfig.json b/ab-testing/deploy-lambda/tsconfig.json similarity index 100% rename from ab-testing/dictionary-deploy-lambda/tsconfig.json rename to ab-testing/deploy-lambda/tsconfig.json diff --git a/ab-testing/eslint.config.mjs b/ab-testing/eslint.config.mjs index b2e76c921e3..c0416ccb8d1 100644 --- a/ab-testing/eslint.config.mjs +++ b/ab-testing/eslint.config.mjs @@ -3,10 +3,10 @@ import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ globalIgnores([ - "frontend", + "frontend/output/**", "eslint.config.mjs", - "index.ts", - "dictionary-deploy-lambda/dist", + "config/index.ts", + "deploy-lambda/dist/**", ]), ...guardian.configs.recommended, { diff --git a/ab-testing/frontend/deno.lock b/ab-testing/frontend/deno.lock deleted file mode 100644 index 6f144b6b22f..00000000000 --- a/ab-testing/frontend/deno.lock +++ /dev/null @@ -1,615 +0,0 @@ -{ - "version": "5", - "specifiers": { - "npm:@sveltejs/adapter-auto@6": "6.0.1_@sveltejs+kit@2.21.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.28.6____acorn@8.14.1___vite@6.3.5____picomatch@4.0.2__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2__acorn@8.14.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2", - "npm:@sveltejs/adapter-static@^3.0.8": "3.0.8_@sveltejs+kit@2.21.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.28.6____acorn@8.14.1___vite@6.3.5____picomatch@4.0.2__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2__acorn@8.14.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2", - "npm:@sveltejs/kit@^2.16.0": "2.21.0_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2_acorn@8.14.1", - "npm:@sveltejs/vite-plugin-svelte@5": "5.0.3_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2", - "npm:svelte-check@4": "4.1.7_svelte@5.28.6__acorn@8.14.1_typescript@5.8.3", - "npm:svelte@5": "5.28.6_acorn@8.14.1", - "npm:typescript@5": "5.8.3", - "npm:vite@^6.2.6": "6.3.5_picomatch@4.0.2" - }, - "npm": { - "@ampproject/remapping@2.3.0": { - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dependencies": [ - "@jridgewell/gen-mapping", - "@jridgewell/trace-mapping" - ] - }, - "@esbuild/aix-ppc64@0.25.4": { - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "os": ["aix"], - "cpu": ["ppc64"] - }, - "@esbuild/android-arm64@0.25.4": { - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "os": ["android"], - "cpu": ["arm64"] - }, - "@esbuild/android-arm@0.25.4": { - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "os": ["android"], - "cpu": ["arm"] - }, - "@esbuild/android-x64@0.25.4": { - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "os": ["android"], - "cpu": ["x64"] - }, - "@esbuild/darwin-arm64@0.25.4": { - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "os": ["darwin"], - "cpu": ["arm64"] - }, - "@esbuild/darwin-x64@0.25.4": { - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "os": ["darwin"], - "cpu": ["x64"] - }, - "@esbuild/freebsd-arm64@0.25.4": { - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "os": ["freebsd"], - "cpu": ["arm64"] - }, - "@esbuild/freebsd-x64@0.25.4": { - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "os": ["freebsd"], - "cpu": ["x64"] - }, - "@esbuild/linux-arm64@0.25.4": { - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@esbuild/linux-arm@0.25.4": { - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "os": ["linux"], - "cpu": ["arm"] - }, - "@esbuild/linux-ia32@0.25.4": { - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "os": ["linux"], - "cpu": ["ia32"] - }, - "@esbuild/linux-loong64@0.25.4": { - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "os": ["linux"], - "cpu": ["loong64"] - }, - "@esbuild/linux-mips64el@0.25.4": { - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "os": ["linux"], - "cpu": ["mips64el"] - }, - "@esbuild/linux-ppc64@0.25.4": { - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "os": ["linux"], - "cpu": ["ppc64"] - }, - "@esbuild/linux-riscv64@0.25.4": { - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "os": ["linux"], - "cpu": ["riscv64"] - }, - "@esbuild/linux-s390x@0.25.4": { - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "os": ["linux"], - "cpu": ["s390x"] - }, - "@esbuild/linux-x64@0.25.4": { - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@esbuild/netbsd-arm64@0.25.4": { - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "os": ["netbsd"], - "cpu": ["arm64"] - }, - "@esbuild/netbsd-x64@0.25.4": { - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "os": ["netbsd"], - "cpu": ["x64"] - }, - "@esbuild/openbsd-arm64@0.25.4": { - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "os": ["openbsd"], - "cpu": ["arm64"] - }, - "@esbuild/openbsd-x64@0.25.4": { - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "os": ["openbsd"], - "cpu": ["x64"] - }, - "@esbuild/sunos-x64@0.25.4": { - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "os": ["sunos"], - "cpu": ["x64"] - }, - "@esbuild/win32-arm64@0.25.4": { - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "os": ["win32"], - "cpu": ["arm64"] - }, - "@esbuild/win32-ia32@0.25.4": { - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "os": ["win32"], - "cpu": ["ia32"] - }, - "@esbuild/win32-x64@0.25.4": { - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "os": ["win32"], - "cpu": ["x64"] - }, - "@jridgewell/gen-mapping@0.3.8": { - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dependencies": [ - "@jridgewell/set-array", - "@jridgewell/sourcemap-codec", - "@jridgewell/trace-mapping" - ] - }, - "@jridgewell/resolve-uri@3.1.2": { - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" - }, - "@jridgewell/set-array@1.2.1": { - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" - }, - "@jridgewell/sourcemap-codec@1.5.0": { - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "@jridgewell/trace-mapping@0.3.25": { - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dependencies": [ - "@jridgewell/resolve-uri", - "@jridgewell/sourcemap-codec" - ] - }, - "@polka/url@1.0.0-next.29": { - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" - }, - "@rollup/rollup-android-arm-eabi@4.40.2": { - "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", - "os": ["android"], - "cpu": ["arm"] - }, - "@rollup/rollup-android-arm64@4.40.2": { - "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", - "os": ["android"], - "cpu": ["arm64"] - }, - "@rollup/rollup-darwin-arm64@4.40.2": { - "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", - "os": ["darwin"], - "cpu": ["arm64"] - }, - "@rollup/rollup-darwin-x64@4.40.2": { - "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", - "os": ["darwin"], - "cpu": ["x64"] - }, - "@rollup/rollup-freebsd-arm64@4.40.2": { - "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", - "os": ["freebsd"], - "cpu": ["arm64"] - }, - "@rollup/rollup-freebsd-x64@4.40.2": { - "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", - "os": ["freebsd"], - "cpu": ["x64"] - }, - "@rollup/rollup-linux-arm-gnueabihf@4.40.2": { - "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", - "os": ["linux"], - "cpu": ["arm"] - }, - "@rollup/rollup-linux-arm-musleabihf@4.40.2": { - "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", - "os": ["linux"], - "cpu": ["arm"] - }, - "@rollup/rollup-linux-arm64-gnu@4.40.2": { - "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@rollup/rollup-linux-arm64-musl@4.40.2": { - "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@rollup/rollup-linux-loongarch64-gnu@4.40.2": { - "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", - "os": ["linux"], - "cpu": ["loong64"] - }, - "@rollup/rollup-linux-powerpc64le-gnu@4.40.2": { - "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", - "os": ["linux"], - "cpu": ["ppc64"] - }, - "@rollup/rollup-linux-riscv64-gnu@4.40.2": { - "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", - "os": ["linux"], - "cpu": ["riscv64"] - }, - "@rollup/rollup-linux-riscv64-musl@4.40.2": { - "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", - "os": ["linux"], - "cpu": ["riscv64"] - }, - "@rollup/rollup-linux-s390x-gnu@4.40.2": { - "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", - "os": ["linux"], - "cpu": ["s390x"] - }, - "@rollup/rollup-linux-x64-gnu@4.40.2": { - "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@rollup/rollup-linux-x64-musl@4.40.2": { - "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@rollup/rollup-win32-arm64-msvc@4.40.2": { - "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", - "os": ["win32"], - "cpu": ["arm64"] - }, - "@rollup/rollup-win32-ia32-msvc@4.40.2": { - "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", - "os": ["win32"], - "cpu": ["ia32"] - }, - "@rollup/rollup-win32-x64-msvc@4.40.2": { - "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", - "os": ["win32"], - "cpu": ["x64"] - }, - "@sveltejs/acorn-typescript@1.0.5_acorn@8.14.1": { - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dependencies": [ - "acorn" - ] - }, - "@sveltejs/adapter-auto@6.0.1_@sveltejs+kit@2.21.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.28.6____acorn@8.14.1___vite@6.3.5____picomatch@4.0.2__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2__acorn@8.14.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2": { - "integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==", - "dependencies": [ - "@sveltejs/kit" - ] - }, - "@sveltejs/adapter-static@3.0.8_@sveltejs+kit@2.21.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.28.6____acorn@8.14.1___vite@6.3.5____picomatch@4.0.2__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2__acorn@8.14.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2": { - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", - "dependencies": [ - "@sveltejs/kit" - ] - }, - "@sveltejs/kit@2.21.0_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2_acorn@8.14.1": { - "integrity": "sha512-kvu4h9qXduiPk1Q1oqFKDLFGu/7mslEYbVaqpbBcBxjlRJnvNCFwEvEwKt0Mx9TtSi8J77xRelvJobrGlst4nQ==", - "dependencies": [ - "@sveltejs/acorn-typescript", - "@sveltejs/vite-plugin-svelte", - "@types/cookie", - "acorn", - "cookie", - "devalue", - "esm-env", - "kleur", - "magic-string", - "mrmime", - "sade", - "set-cookie-parser", - "sirv", - "svelte", - "vite" - ], - "bin": true - }, - "@sveltejs/vite-plugin-svelte-inspector@4.0.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.6___acorn@8.14.1__vite@6.3.5___picomatch@4.0.2_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2": { - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", - "dependencies": [ - "@sveltejs/vite-plugin-svelte", - "debug", - "svelte", - "vite" - ] - }, - "@sveltejs/vite-plugin-svelte@5.0.3_svelte@5.28.6__acorn@8.14.1_vite@6.3.5__picomatch@4.0.2": { - "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", - "dependencies": [ - "@sveltejs/vite-plugin-svelte-inspector", - "debug", - "deepmerge", - "kleur", - "magic-string", - "svelte", - "vite", - "vitefu" - ] - }, - "@types/cookie@0.6.0": { - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" - }, - "@types/estree@1.0.7": { - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" - }, - "acorn@8.14.1": { - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "bin": true - }, - "aria-query@5.3.2": { - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" - }, - "axobject-query@4.1.0": { - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" - }, - "chokidar@4.0.3": { - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dependencies": [ - "readdirp" - ] - }, - "clsx@2.1.1": { - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" - }, - "cookie@0.6.0": { - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" - }, - "debug@4.4.1": { - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": [ - "ms" - ] - }, - "deepmerge@4.3.1": { - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" - }, - "devalue@5.1.1": { - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==" - }, - "esbuild@0.25.4": { - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", - "optionalDependencies": [ - "@esbuild/aix-ppc64", - "@esbuild/android-arm", - "@esbuild/android-arm64", - "@esbuild/android-x64", - "@esbuild/darwin-arm64", - "@esbuild/darwin-x64", - "@esbuild/freebsd-arm64", - "@esbuild/freebsd-x64", - "@esbuild/linux-arm", - "@esbuild/linux-arm64", - "@esbuild/linux-ia32", - "@esbuild/linux-loong64", - "@esbuild/linux-mips64el", - "@esbuild/linux-ppc64", - "@esbuild/linux-riscv64", - "@esbuild/linux-s390x", - "@esbuild/linux-x64", - "@esbuild/netbsd-arm64", - "@esbuild/netbsd-x64", - "@esbuild/openbsd-arm64", - "@esbuild/openbsd-x64", - "@esbuild/sunos-x64", - "@esbuild/win32-arm64", - "@esbuild/win32-ia32", - "@esbuild/win32-x64" - ], - "scripts": true, - "bin": true - }, - "esm-env@1.2.2": { - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" - }, - "esrap@1.4.6": { - "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", - "dependencies": [ - "@jridgewell/sourcemap-codec" - ] - }, - "fdir@6.4.4_picomatch@4.0.2": { - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dependencies": [ - "picomatch" - ], - "optionalPeers": [ - "picomatch" - ] - }, - "fsevents@2.3.3": { - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "os": ["darwin"], - "scripts": true - }, - "is-reference@3.0.3": { - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dependencies": [ - "@types/estree" - ] - }, - "kleur@4.1.5": { - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" - }, - "locate-character@3.0.0": { - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" - }, - "magic-string@0.30.17": { - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dependencies": [ - "@jridgewell/sourcemap-codec" - ] - }, - "mri@1.2.0": { - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" - }, - "mrmime@2.0.1": { - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==" - }, - "ms@2.1.3": { - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "nanoid@3.3.11": { - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "bin": true - }, - "picocolors@1.1.1": { - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "picomatch@4.0.2": { - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" - }, - "postcss@8.5.3": { - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dependencies": [ - "nanoid", - "picocolors", - "source-map-js" - ] - }, - "readdirp@4.1.2": { - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" - }, - "rollup@4.40.2": { - "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", - "dependencies": [ - "@types/estree" - ], - "optionalDependencies": [ - "@rollup/rollup-android-arm-eabi", - "@rollup/rollup-android-arm64", - "@rollup/rollup-darwin-arm64", - "@rollup/rollup-darwin-x64", - "@rollup/rollup-freebsd-arm64", - "@rollup/rollup-freebsd-x64", - "@rollup/rollup-linux-arm-gnueabihf", - "@rollup/rollup-linux-arm-musleabihf", - "@rollup/rollup-linux-arm64-gnu", - "@rollup/rollup-linux-arm64-musl", - "@rollup/rollup-linux-loongarch64-gnu", - "@rollup/rollup-linux-powerpc64le-gnu", - "@rollup/rollup-linux-riscv64-gnu", - "@rollup/rollup-linux-riscv64-musl", - "@rollup/rollup-linux-s390x-gnu", - "@rollup/rollup-linux-x64-gnu", - "@rollup/rollup-linux-x64-musl", - "@rollup/rollup-win32-arm64-msvc", - "@rollup/rollup-win32-ia32-msvc", - "@rollup/rollup-win32-x64-msvc", - "fsevents" - ], - "bin": true - }, - "sade@1.8.1": { - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dependencies": [ - "mri" - ] - }, - "set-cookie-parser@2.7.1": { - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" - }, - "sirv@3.0.1": { - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", - "dependencies": [ - "@polka/url", - "mrmime", - "totalist" - ] - }, - "source-map-js@1.2.1": { - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" - }, - "svelte-check@4.1.7_svelte@5.28.6__acorn@8.14.1_typescript@5.8.3": { - "integrity": "sha512-1jX4BzXrQJhC/Jt3SqYf6Ntu//vmfc6VWp07JkRfK2nn+22yIblspVUo96gzMkg0Zov8lQicxhxsMzOctwcMQQ==", - "dependencies": [ - "@jridgewell/trace-mapping", - "chokidar", - "fdir", - "picocolors", - "sade", - "svelte", - "typescript" - ], - "bin": true - }, - "svelte@5.28.6_acorn@8.14.1": { - "integrity": "sha512-9qqr7mw8YR9PAnxGFfzCK6PUlNGtns7wVavrhnxyf3fpB1mP/Ol55Z2UnIapsSzNNl3k9qw7cZ22PdE8+xT/jQ==", - "dependencies": [ - "@ampproject/remapping", - "@jridgewell/sourcemap-codec", - "@sveltejs/acorn-typescript", - "@types/estree", - "acorn", - "aria-query", - "axobject-query", - "clsx", - "esm-env", - "esrap", - "is-reference", - "locate-character", - "magic-string", - "zimmerframe" - ] - }, - "tinyglobby@0.2.13_picomatch@4.0.2": { - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dependencies": [ - "fdir", - "picomatch" - ] - }, - "totalist@3.0.1": { - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" - }, - "typescript@5.8.3": { - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "bin": true - }, - "vite@6.3.5_picomatch@4.0.2": { - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dependencies": [ - "esbuild", - "fdir", - "picomatch", - "postcss", - "rollup", - "tinyglobby" - ], - "optionalDependencies": [ - "fsevents" - ], - "bin": true - }, - "vitefu@1.0.6_vite@6.3.5__picomatch@4.0.2": { - "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", - "dependencies": [ - "vite" - ], - "optionalPeers": [ - "vite" - ] - }, - "zimmerframe@1.1.2": { - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==" - } - }, - "workspace": { - "packageJson": { - "dependencies": [ - "npm:@sveltejs/adapter-auto@6", - "npm:@sveltejs/adapter-static@^3.0.8", - "npm:@sveltejs/kit@^2.16.0", - "npm:@sveltejs/vite-plugin-svelte@5", - "npm:svelte-check@4", - "npm:svelte@5", - "npm:typescript@5", - "npm:vite@^6.2.6" - ] - } - } -} diff --git a/ab-testing/frontend/package.json b/ab-testing/frontend/package.json index e0b67082b73..25e946ee7ab 100644 --- a/ab-testing/frontend/package.json +++ b/ab-testing/frontend/package.json @@ -12,7 +12,7 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" }, "devDependencies": { - "@guardian/ab-testing": "workspace:ab-testing", + "@guardian/ab-testing-config": "workspace:ab-testing-config", "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/kit": "^2.16.0", diff --git a/ab-testing/frontend/src/routes/+page.svelte b/ab-testing/frontend/src/routes/+page.svelte index 6da266f7452..a50f702943c 100644 --- a/ab-testing/frontend/src/routes/+page.svelte +++ b/ab-testing/frontend/src/routes/+page.svelte @@ -1,5 +1,5 @@ diff --git a/ab-testing/tsconfig.json b/ab-testing/tsconfig.json index 43b84c015db..7e6d49d6214 100644 --- a/ab-testing/tsconfig.json +++ b/ab-testing/tsconfig.json @@ -7,5 +7,5 @@ "moduleResolution": "nodenext", "module": "nodenext" }, - "exclude": ["node_modules", "index.ts"] + "exclude": ["node_modules", "config/index.ts"] } diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index cde443a4a1a..3433de3cd7f 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -27,7 +27,7 @@ "@emotion/react": "11.14.0", "@emotion/server": "11.11.0", "@guardian/ab-core": "8.0.0", - "@guardian/ab-testing": "workspace:ab-testing", + "@guardian/ab-testing-config": "workspace:ab-testing-config", "@guardian/braze-components": "22.2.0", "@guardian/bridget": "8.7.0", "@guardian/browserslist-config": "6.1.0", diff --git a/dotcom-rendering/src/components/Metrics.importable.tsx b/dotcom-rendering/src/components/Metrics.importable.tsx index 5f3c80ee169..d4961c2b6e7 100644 --- a/dotcom-rendering/src/components/Metrics.importable.tsx +++ b/dotcom-rendering/src/components/Metrics.importable.tsx @@ -1,5 +1,5 @@ import type { ABTest, ABTestAPI } from '@guardian/ab-core'; -import { activeABtests } from '@guardian/ab-testing'; +import { activeABtests } from '@guardian/ab-testing-config'; import { bypassCommercialMetricsSampling, EventTimer, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 976d3fca29d..bce21af196b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,11 +35,7 @@ importers: specifier: 2.6.2 version: 2.6.2 - ab-testing: - dependencies: - superstruct: - specifier: 2.0.2 - version: 2.0.2 + ab-testing/cdk: devDependencies: '@aws-sdk/client-s3': specifier: 3.931.0 @@ -78,20 +74,8 @@ importers: specifier: 5.9.3 version: 5.9.3 - ab-testing/dictionary-deploy-lambda: + ab-testing/config: dependencies: - '@rollup/plugin-json': - specifier: 6.1.0 - version: 6.1.0(rollup@4.53.2) - cfn-response: - specifier: 1.0.1 - version: 1.0.1 - esbuild: - specifier: 0.27.0 - version: 0.27.0 - rollup-plugin-esbuild: - specifier: 6.2.1 - version: 6.2.1(esbuild@0.27.0)(rollup@4.53.2) superstruct: specifier: 2.0.2 version: 2.0.2 @@ -99,6 +83,49 @@ importers: '@aws-sdk/client-s3': specifier: 3.931.0 version: 3.931.0 + '@aws-sdk/client-ssm': + specifier: 3.621.0 + version: 3.621.0 + '@guardian/cdk': + specifier: 62.0.1 + version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) + '@guardian/eslint-config': + specifier: 12.0.1 + version: 12.0.1(eslint-plugin-import@2.29.1(eslint@9.39.1))(eslint@9.39.1)(typescript@5.9.3) + '@guardian/tsconfig': + specifier: 1.0.1 + version: 1.0.1 + '@types/node': + specifier: 22.17.0 + version: 22.17.0 + aws-cdk: + specifier: 2.1030.0 + version: 2.1030.0 + aws-cdk-lib: + specifier: 2.220.0 + version: 2.220.0(constructs@10.4.2) + eslint: + specifier: 9.39.1 + version: 9.39.1 + prettier: + specifier: 3.0.3 + version: 3.0.3 + source-map-support: + specifier: 0.5.21 + version: 0.5.21 + typescript: + specifier: 5.9.3 + version: 5.9.3 + + ab-testing/deploy-lambda: + dependencies: + '@aws-sdk/client-s3': + specifier: 3.931.0 + version: 3.931.0 + superstruct: + specifier: 2.0.2 + version: 2.0.2 + devDependencies: '@guardian/cdk': specifier: 62.0.1 version: 62.0.1(aws-cdk-lib@2.220.0(constructs@10.4.2))(aws-cdk@2.1030.0)(constructs@10.4.2) @@ -111,18 +138,15 @@ importers: '@rollup/plugin-commonjs': specifier: 29.0.0 version: 29.0.0(rollup@4.53.2) + '@rollup/plugin-json': + specifier: 6.1.0 + version: 6.1.0(rollup@4.53.2) '@rollup/plugin-node-resolve': specifier: 16.0.3 version: 16.0.3(rollup@4.53.2) - '@rollup/plugin-typescript': - specifier: 12.3.0 - version: 12.3.0(rollup@4.53.2)(tslib@2.8.1)(typescript@5.9.3) '@types/aws-lambda': specifier: 8.10.158 version: 8.10.158 - '@types/cfn-response': - specifier: 1.0.8 - version: 1.0.8 '@types/node': specifier: 22.17.0 version: 22.17.0 @@ -132,6 +156,9 @@ importers: aws-lambda: specifier: 1.0.7 version: 1.0.7 + esbuild: + specifier: 0.27.0 + version: 0.27.0 eslint: specifier: 9.39.1 version: 9.39.1 @@ -141,15 +168,18 @@ importers: rollup: specifier: 4.53.2 version: 4.53.2 + rollup-plugin-esbuild: + specifier: 6.2.1 + version: 6.2.1(esbuild@0.27.0)(rollup@4.53.2) typescript: specifier: 5.9.3 version: 5.9.3 ab-testing/frontend: devDependencies: - '@guardian/ab-testing': - specifier: workspace:ab-testing - version: link:.. + '@guardian/ab-testing-config': + specifier: workspace:ab-testing-config + version: link:../config '@sveltejs/adapter-auto': specifier: ^6.0.0 version: 6.1.1(@sveltejs/kit@2.46.5(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.40.0)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0)))(svelte@5.40.0)(vite@6.4.0(@types/node@22.17.0)(terser@5.44.0))) @@ -417,9 +447,9 @@ importers: '@guardian/ab-core': specifier: 8.0.0 version: 8.0.0(tslib@2.6.2)(typescript@5.5.3) - '@guardian/ab-testing': - specifier: workspace:ab-testing - version: link:../ab-testing + '@guardian/ab-testing-config': + specifier: workspace:ab-testing-config + version: link:../ab-testing/config '@guardian/braze-components': specifier: 22.2.0 version: 22.2.0(@emotion/react@11.14.0(@types/react@18.3.1)(react@18.3.1))(@guardian/libs@26.1.0(@guardian/ophan-tracker-js@2.6.2)(tslib@2.6.2)(typescript@5.5.3))(@guardian/source@11.3.0(@emotion/react@11.14.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)(tslib@2.6.2)(typescript@5.5.3))(react@18.3.1) @@ -3150,19 +3180,6 @@ packages: rollup: optional: true - '@rollup/plugin-typescript@12.3.0': - resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 - tslib: '*' - typescript: '>=3.7.0' - peerDependenciesMeta: - rollup: - optional: true - tslib: - optional: true - '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -3356,6 +3373,7 @@ packages: '@smithy/core@3.18.3': resolution: {integrity: sha512-qqpNskkbHOSfrbFbjhYj5o8VMXO26fvN1K/+HbCzUNlTuxgNcPRouUDNm+7D6CkN244WG7aK533Ne18UtJEgAA==} engines: {node: '>=18.0.0'} + deprecated: Please upgrade your lockfile to use the latest 3.x version of @smithy/core for various fixes, see https://github.com/smithy-lang/smithy-typescript/blob/main/packages/core/CHANGELOG.md '@smithy/credential-provider-imds@3.2.0': resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==} @@ -4245,9 +4263,6 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} - '@types/cfn-response@1.0.8': - resolution: {integrity: sha512-b9YttPRwTVcn/r65BcxrXqrVXpBtns3IlwPgE5XfzFoXBSBhimw7ZTbpvdsP39nsxVTmvV7IUN34/9FBQyfe2w==} - '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -5393,9 +5408,6 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - cfn-response@1.0.1: - resolution: {integrity: sha512-mHfgHUcGpIbmioV/gQ//5V+b8em/ZKFwAgzmbycflJ1q0AU66wlhIgarkYc4JXGgIca+0lxg+XEKO4ZwyZedaw==} - chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} @@ -14237,15 +14249,6 @@ snapshots: optionalDependencies: rollup: 4.53.2 - '@rollup/plugin-typescript@12.3.0(rollup@4.53.2)(tslib@2.8.1)(typescript@5.9.3)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.53.2) - resolve: 1.22.10 - typescript: 5.9.3 - optionalDependencies: - rollup: 4.53.2 - tslib: 2.8.1 - '@rollup/pluginutils@5.3.0(rollup@4.53.2)': dependencies: '@types/estree': 1.0.8 @@ -15664,10 +15667,6 @@ snapshots: dependencies: '@types/node': 22.17.0 - '@types/cfn-response@1.0.8': - dependencies: - '@types/aws-lambda': 8.10.158 - '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -17187,8 +17186,6 @@ snapshots: ccount@2.0.1: {} - cfn-response@1.0.1: {} - chai@5.2.0: dependencies: assertion-error: 2.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 79d7dc83243..5e0c01b31fe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,4 @@ packages: - 'apps-rendering' - 'dotcom-rendering' - - 'ab-testing' - - 'ab-testing/frontend' - - 'ab-testing/dictionary-deploy-lambda' + - 'ab-testing/*' From 35e6107e1322fdbb883a4ef823e409f0e887126c Mon Sep 17 00:00:00 2001 From: Jake Lee Kennedy Date: Thu, 20 Nov 2025 14:01:03 +0000 Subject: [PATCH 31/31] Add names to Riff-Raff jobs and create tsconfig for ab-testing --- .github/workflows/ab-testing-ci.yml | 1 + .github/workflows/ab-testing-lambda-ci.yml | 2 ++ ab-testing/config/tsconfig.json | 3 +++ 3 files changed, 6 insertions(+) create mode 100644 ab-testing/config/tsconfig.json diff --git a/.github/workflows/ab-testing-ci.yml b/.github/workflows/ab-testing-ci.yml index 1297b75adf0..45558648e79 100644 --- a/.github/workflows/ab-testing-ci.yml +++ b/.github/workflows/ab-testing-ci.yml @@ -75,6 +75,7 @@ jobs: if-no-files-found: error riff-raff: + name: Riff-Raff Artifacts runs-on: ubuntu-latest needs: [config-ci, ui-ci] permissions: diff --git a/.github/workflows/ab-testing-lambda-ci.yml b/.github/workflows/ab-testing-lambda-ci.yml index 3e7d6951846..74cc87080ac 100644 --- a/.github/workflows/ab-testing-lambda-ci.yml +++ b/.github/workflows/ab-testing-lambda-ci.yml @@ -13,6 +13,7 @@ on: jobs: ci: + name: CI runs-on: ubuntu-latest defaults: run: @@ -41,6 +42,7 @@ jobs: path: ab-testing/deploy-lambda/dist/lambda.zip riff-raff: + name: Riff-Raff Artifacts runs-on: ubuntu-latest needs: [ci] permissions: diff --git a/ab-testing/config/tsconfig.json b/ab-testing/config/tsconfig.json new file mode 100644 index 00000000000..e0d44119581 --- /dev/null +++ b/ab-testing/config/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json", +}