diff --git a/README.md b/README.md index 4d04e7a7..ac65c300 100644 --- a/README.md +++ b/README.md @@ -92,11 +92,25 @@ appSync: - [Variable Substitutions](doc/substitutions.md) - [Caching](doc/caching.md) - [Web Application Firewall (WAF)](doc/WAF.md) +- [Testing Resolvers](doc/testing-resolvers.md) # CLI This plugin adds some useful CLI commands. See [CLI commands documentation](doc/commands.md) +| Command | Description | +| ------------------------------- | -------------------------------------------------------- | +| `sls appsync validate-schema` | Validate the GraphQL schema | +| `sls appsync get-introspection` | Export the introspection schema (JSON or SDL) | +| `sls appsync flush-cache` | Flush the API cache | +| `sls appsync console` | Open the AWS AppSync console | +| `sls appsync cloudwatch` | Open CloudWatch logs | +| `sls appsync logs` | Stream logs to stdout | +| `sls appsync evaluate` | Evaluate a JS resolver or VTL template without deploying | +| `sls appsync env get` | Get runtime environment variables of the deployed API | +| `sls appsync env set` | Set a runtime environment variable on the deployed API | +| `sls appsync domain *` | Manage custom domains | + # Variables This plugin exports some handy variables that you can use in your yml files to reference some values generated by CloudFormation: diff --git a/doc/commands.md b/doc/commands.md index 0b5f7777..48534be7 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -2,6 +2,21 @@ This plugin provides some useful commands to explore and manage your API. +## Quick reference + +| Command | Description | +| ------------------------------- | ----------------------------------- | +| `sls appsync validate-schema` | Validate the GraphQL schema | +| `sls appsync get-introspection` | Export the introspection schema | +| `sls appsync flush-cache` | Flush the API cache | +| `sls appsync console` | Open the AWS console | +| `sls appsync cloudwatch` | Open CloudWatch logs | +| `sls appsync logs` | Stream logs to stdout | +| `sls appsync evaluate` | Evaluate a resolver or VTL template | +| `sls appsync env get` | Get API environment variables | +| `sls appsync env set` | Set an API environment variable | +| `sls appsync domain *` | Manage custom domains | + ## `validate-schema` This commands allows you to validate your GraphQL schema. @@ -62,6 +77,69 @@ Outputs the logs of the AppSync API to stdout. sls appsync logs --filter '86771d0c-c0f3-4f54-b048-793a233e3ed9' ``` +## `evaluate` + +Evaluate a resolver or VTL mapping template against a context without deploying. Uses the AppSync `EvaluateCode` and `EvaluateMappingTemplate` APIs. + +**Evaluate a JS resolver (APPSYNC_JS runtime)** + +The resolver must be a `UNIT` resolver with a `code` property defined in your configuration. + +**Options** + +- `--type` or `-t`: GraphQL type (e.g. `Query`) +- `--field` or `-f`: GraphQL field (e.g. `getUser`) +- `--function`: Function to evaluate: `request` or `response`. Default: `request` +- `--context` or `-c`: Path to a JSON context file, or an inline JSON string. Default: `{}` + +```bash +sls appsync evaluate --type Query --field getUser --function request +sls appsync evaluate --type Query --field getUser --function response --context context.json +sls appsync evaluate --type Mutation --field createPost --context '{"arguments":{"title":"Hello"}}' +``` + +**Evaluate a VTL mapping template** + +- `--template`: Path to a `.vtl` template file +- `--context` or `-c`: Path to a JSON context file, or an inline JSON string. Default: `{}` + +```bash +sls appsync evaluate --template templates/getUser.request.vtl +sls appsync evaluate --template templates/getUser.request.vtl --context context.json +``` + +The command prints the evaluation result to stdout. Errors (including line/column info for JS) are printed to stderr. + +For a detailed guide on testing unit and pipeline resolvers with this command — including fixture structure, a shell script for chaining pipeline steps, and Jest integration test examples — see [Testing Resolvers](testing-resolvers.md). + +## `env` + +Manage the environment variables of the deployed AppSync API at runtime, without redeploying. Uses the `GetGraphqlApiEnvironmentVariables` and `PutGraphqlApiEnvironmentVariables` APIs. + +> **Note:** These commands operate on the _deployed_ API. To set environment variables that are baked into the CloudFormation stack, use the [`environment`](general-config.md) configuration key instead. + +### `env get` + +Prints all environment variables currently set on the deployed API. + +```bash +sls appsync env get +``` + +### `env set` + +Adds or updates a single environment variable on the deployed API. Existing variables are preserved — only the specified key is changed. + +**Options** + +- `--key` or `-k`: Environment variable key (required) +- `--value` or `-v`: Environment variable value (required) + +```bash +sls appsync env set --key TABLE_NAME --value prod-table +sls appsync env set --key STAGE --value production +``` + ## `domain` Manage the domain for this AppSync API. diff --git a/doc/testing-resolvers.md b/doc/testing-resolvers.md new file mode 100644 index 00000000..75c35c84 --- /dev/null +++ b/doc/testing-resolvers.md @@ -0,0 +1,438 @@ +# Testing Resolvers with `evaluate` + +The `sls appsync evaluate` command calls the AppSync `EvaluateCode` and `EvaluateMappingTemplate` APIs directly. This lets you test resolver logic against a known context **without deploying** and without executing the actual data source (DynamoDB, Lambda, etc.). + +> **Requirements:** An active AWS connection with `appsync:EvaluateCode` and/or `appsync:EvaluateMappingTemplate` IAM permissions. + +--- + +## How evaluation works + +AppSync executes only the resolver code itself. It does **not**: + +- call the data source (DynamoDB, Lambda, HTTP, …) +- execute other pipeline functions +- validate the GraphQL schema + +What you get back is the **request mapping** (what would be sent to the data source) or the **response mapping** (what would be returned to the client), depending on which function you evaluate. + +--- + +## Unit resolver + +A unit resolver has a single `request` function and a single `response` function. + +``` +context → request() → [data source — not called] → response() → result +``` + +Evaluate each function independently: + +```bash +# Evaluate the request mapping +sls appsync evaluate \ + --type Query \ + --field getUser \ + --function request \ + --context '{"arguments":{"id":"abc-123"},"identity":{"sub":"user-1"}}' + +# Evaluate the response mapping (pass a mock data source result via context.result) +sls appsync evaluate \ + --type Query \ + --field getUser \ + --function response \ + --context '{"arguments":{"id":"abc-123"},"result":{"id":"abc-123","name":"Alice"}}' +``` + +--- + +## Pipeline resolver + +A pipeline resolver chains multiple functions. Each function has its own `request` and `response`, and passes data to the next via `ctx.stash` and `ctx.prev.result`. + +``` +context + │ + ▼ +[pipeline request handler] ← optional JS wrapper + │ + ▼ +function 1: request() → [data source — not called] → response() + │ (result stored in ctx.prev.result, stash carried forward) + ▼ +function 2: request() → [data source — not called] → response() + │ + ▼ +[pipeline response handler] ← optional JS wrapper + │ + ▼ +result +``` + +Because `EvaluateCode` runs **one function at a time**, you test a pipeline by chaining evaluations manually: the output of one function becomes part of the context for the next. + +### Step-by-step example + +Suppose you have a `Mutation.createPost` pipeline with two functions: + +| Step | Function | File | +| ---- | --------------- | ---------------------------- | +| 1 | `validateInput` | `functions/validateInput.js` | +| 2 | `savePost` | `functions/savePost.js` | + +**Step 1 — evaluate `validateInput` request:** + +```bash +sls appsync evaluate \ + --type Mutation \ + --field createPost \ + --function request \ + --context '{ + "arguments": { "title": "Hello World", "body": "Content" }, + "identity": { "sub": "user-1" }, + "stash": {} + }' +# Output: the DynamoDB (or other DS) request object that validateInput would send +``` + +**Step 2 — evaluate `validateInput` response** (mock the data source result): + +```bash +sls appsync evaluate \ + --type Mutation \ + --field createPost \ + --function response \ + --context '{ + "arguments": { "title": "Hello World", "body": "Content" }, + "identity": { "sub": "user-1" }, + "stash": {}, + "result": { "valid": true } + }' +# Output: what validateInput puts into ctx.prev.result for the next function +``` + +**Step 3 — evaluate `savePost` request** (carry forward stash and prev.result): + +```bash +sls appsync evaluate \ + --type Mutation \ + --field createPost \ + --function request \ + --context '{ + "arguments": { "title": "Hello World", "body": "Content" }, + "identity": { "sub": "user-1" }, + "stash": { "validated": true }, + "prev": { "result": { "valid": true } } + }' +# Output: the DynamoDB PutItem request that savePost would send +``` + +--- + +## Automating pipeline tests with a script + +The pattern above maps naturally to a shell script. Store your test fixtures as JSON files alongside your resolver code: + +``` +functions/ + validateInput.js + savePost.js +tests/ + createPost/ + step1-validateInput-request.ctx.json ← input context + step1-validateInput-request.expected.json ← expected output + step2-validateInput-response.ctx.json + step2-validateInput-response.expected.json + step3-savePost-request.ctx.json + step3-savePost-request.expected.json +``` + +### `scripts/test-pipeline.sh` + +```bash +#!/usr/bin/env bash +# Usage: ./scripts/test-pipeline.sh Mutation createPost +# Runs all test steps for a pipeline resolver in order. + +set -euo pipefail + +TYPE="${1:?Usage: $0 }" +FIELD="${2:?Usage: $0 }" +TEST_DIR="tests/${FIELD}" +FAILED=0 + +if [ ! -d "$TEST_DIR" ]; then + echo "No test directory found: $TEST_DIR" + exit 1 +fi + +# Collect and sort step files so they run in order (step1, step2, …) +STEPS=$(ls "$TEST_DIR"/*.ctx.json 2>/dev/null | sort) + +if [ -z "$STEPS" ]; then + echo "No context files found in $TEST_DIR" + exit 1 +fi + +for CTX_FILE in $STEPS; do + BASENAME=$(basename "$CTX_FILE" .ctx.json) + EXPECTED_FILE="$TEST_DIR/${BASENAME}.expected.json" + + # Derive --function from filename: ends in -request or -response + if [[ "$BASENAME" == *-request ]]; then + FN="request" + elif [[ "$BASENAME" == *-response ]]; then + FN="response" + else + echo "SKIP $BASENAME (cannot determine function from filename)" + continue + fi + + echo -n "Testing $BASENAME ... " + + RESULT=$(sls appsync evaluate \ + --type "$TYPE" \ + --field "$FIELD" \ + --function "$FN" \ + --context "$CTX_FILE" 2>&1) + + # Check for evaluation errors returned in the result JSON + if echo "$RESULT" | jq -e '.error' > /dev/null 2>&1; then + echo "FAIL (evaluation error)" + echo " $RESULT" + FAILED=1 + continue + fi + + if [ -f "$EXPECTED_FILE" ]; then + DIFF=$(diff \ + <(echo "$RESULT" | jq -S .) \ + <(jq -S . "$EXPECTED_FILE") \ + ) + if [ -n "$DIFF" ]; then + echo "FAIL" + echo "$DIFF" + FAILED=1 + else + echo "PASS" + fi + else + # No expected file — print output so you can create one + echo "OK (no expected file, output below)" + echo "$RESULT" | jq . + fi +done + +if [ "$FAILED" -ne 0 ]; then + echo "" + echo "Some tests failed." + exit 1 +fi + +echo "" +echo "All tests passed." +``` + +Run it: + +```bash +chmod +x scripts/test-pipeline.sh +./scripts/test-pipeline.sh Mutation createPost +./scripts/test-pipeline.sh Query listPosts +``` + +### Generating expected files on first run + +On the first run, omit the `.expected.json` files. The script will print the actual output. Review it, and if it looks correct, save it: + +```bash +sls appsync evaluate \ + --type Mutation \ + --field createPost \ + --function request \ + --context tests/createPost/step1-validateInput-request.ctx.json \ + | jq . > tests/createPost/step1-validateInput-request.expected.json +``` + +From that point on, the script will diff against the saved snapshot. + +--- + +## Using `EvaluateCodeCommand` directly in Node.js + +If you prefer to drive tests from JavaScript (e.g. inside a Jest integration test suite or a custom Node script), you can call the SDK directly. + +### Single function + +```typescript +import { + AppSyncClient, + EvaluateCodeCommand, + RuntimeName, +} from '@aws-sdk/client-appsync'; +import fs from 'fs'; + +const client = new AppSyncClient({ region: 'us-east-1' }); + +async function evaluateFunction( + codePath: string, + context: object, + fn: 'request' | 'response', +) { + const code = fs.readFileSync(codePath, 'utf8'); + + const result = await client.send( + new EvaluateCodeCommand({ + runtime: { name: RuntimeName.APPSYNC_JS, runtimeVersion: '1.0.0' }, + code, + context: JSON.stringify(context), + function: fn, + }), + ); + + if (result.error) { + throw new Error(`Evaluation error in ${codePath}: ${result.error.message}`); + } + + return JSON.parse(result.evaluationResult ?? 'null'); +} +``` + +### Chaining pipeline functions + +```typescript +async function testPipeline() { + // Initial context — what AppSync provides at the start of the pipeline + let ctx: Record = { + arguments: { title: 'Hello World', body: 'Content' }, + identity: { sub: 'user-1' }, + stash: {}, + }; + + // --- Function 1: validateInput --- + // Evaluate request: what would be sent to the data source + const validateRequest = await evaluateFunction( + 'functions/validateInput.js', + ctx, + 'request', + ); + console.log('validateInput request:', validateRequest); + + // Simulate data source response, then evaluate response handler + const validateResponse = await evaluateFunction( + 'functions/validateInput.js', + { ...ctx, result: { valid: true } }, + 'response', + ); + console.log('validateInput response:', validateResponse); + + // Carry forward: prev.result and any stash mutations + ctx = { + ...ctx, + stash: { ...(ctx.stash as object), validated: true }, + prev: { result: validateResponse }, + }; + + // --- Function 2: savePost --- + const saveRequest = await evaluateFunction( + 'functions/savePost.js', + ctx, + 'request', + ); + console.log('savePost request:', saveRequest); + + const saveResponse = await evaluateFunction( + 'functions/savePost.js', + { ...ctx, result: { id: 'post-1', title: 'Hello World' } }, + 'response', + ); + console.log('savePost response:', saveResponse); + + return saveResponse; +} + +testPipeline().then(console.log).catch(console.error); +``` + +### Jest integration test example + +```typescript +import { + AppSyncClient, + EvaluateCodeCommand, + RuntimeName, +} from '@aws-sdk/client-appsync'; + +const client = new AppSyncClient({ + region: process.env.AWS_REGION ?? 'us-east-1', +}); + +async function evaluate( + codePath: string, + context: object, + fn: 'request' | 'response', +) { + const result = await client.send( + new EvaluateCodeCommand({ + runtime: { name: RuntimeName.APPSYNC_JS, runtimeVersion: '1.0.0' }, + code: require('fs').readFileSync(codePath, 'utf8'), + context: JSON.stringify(context), + function: fn, + }), + ); + if (result.error) throw new Error(result.error.message); + return JSON.parse(result.evaluationResult!); +} + +describe('Mutation.createPost pipeline', () => { + const baseCtx = { + arguments: { title: 'Hello', body: 'World' }, + identity: { sub: 'user-1' }, + stash: {}, + }; + + it('validateInput request maps arguments to a validation query', async () => { + const result = await evaluate( + 'functions/validateInput.js', + baseCtx, + 'request', + ); + expect(result.operation).toBe('GetItem'); + expect(result.key).toBeDefined(); + }); + + it('validateInput response passes validation result to stash', async () => { + const result = await evaluate( + 'functions/validateInput.js', + { ...baseCtx, result: { valid: true } }, + 'response', + ); + expect(result).toMatchObject({ valid: true }); + }); + + it('savePost request builds a correct PutItem operation', async () => { + const ctx = { + ...baseCtx, + stash: { validated: true }, + prev: { result: { valid: true } }, + }; + const result = await evaluate('functions/savePost.js', ctx, 'request'); + expect(result.operation).toBe('PutItem'); + expect(result.key.id).toBeDefined(); + }); +}); +``` + +> These are **integration tests** — they call the real AWS API. Run them in a CI stage that has AWS credentials, after unit tests pass. Tag them (e.g. `@integration`) or keep them in a separate Jest project so they don't run on every local `npm test`. + +--- + +## Key limitations to keep in mind + +| Limitation | Detail | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| No data source execution | The actual DynamoDB/Lambda call never happens — you supply mock results via `context.result` | +| No cross-function stash | Each `EvaluateCode` call is isolated — you must manually thread `stash` and `prev.result` between steps | +| No pipeline request/response wrapper | The optional pipeline-level JS wrapper (`before` / `after`) is a separate code file — evaluate it separately if needed | +| Requires AWS credentials | Not suitable for fully offline/local testing | +| One runtime version | Currently only `APPSYNC_JS 1.0.0` is supported for `EvaluateCode`; VTL uses `EvaluateMappingTemplate` | diff --git a/package-lock.json b/package-lock.json index 64289214..a40214cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,18 @@ "version": "0.0.0-development", "license": "MIT", "dependencies": { + "@aws-sdk/client-acm": "3.1039.0", + "@aws-sdk/client-appsync": "3.1039.0", + "@aws-sdk/client-cloudformation": "3.1039.0", + "@aws-sdk/client-cloudwatch-logs": "3.1039.0", + "@aws-sdk/client-route-53": "3.1039.0", + "@aws-sdk/credential-providers": "3.1039.0", "@graphql-tools/merge": "^8.3.12", "@serverless/utils": "^6.8.2", "ajv": "^8.11.2", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "ajv-merge-patch": "^5.0.1", - "aws-sdk": "^2.1265.0", "chalk": "^4.1.2", "esbuild": "^0.17.11", "globby": "^11.1.0", @@ -37,6 +42,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", + "fast-check": "^3.23.2", "husky": "^9.1.7", "jest": "^30.4.2", "lint-staged": "^16.2.3", @@ -88,9 +94,9 @@ } }, "node_modules/@actions/http-client/node_modules/undici": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", - "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", + "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", "dev": true, "license": "MIT", "engines": { @@ -108,7 +114,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -146,11 +151,24 @@ "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -162,11 +180,23 @@ "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -181,7 +211,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -191,7 +220,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", @@ -199,22 +227,86 @@ "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.1039.0.tgz", + "integrity": "sha512-zzx34DawsDmvzbW6qa7qha8AjGueYTyW3EI6zeiwSrO4XTnTPrjhgqR/4geNz8VYuwwgHLTR8L6F5Ut0369TJQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-api-gateway": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-api-gateway/-/client-api-gateway-3.1053.0.tgz", - "integrity": "sha512-OgkpjbW8MQLZDWPF3Uv7ys4K7QSn8/Fu8UTzPYvqyO0cnYTutQQfVpJu1sPqYfqhZpoB8MwFntXen/PKYMem6Q==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-api-gateway/-/client-api-gateway-3.1057.0.tgz", + "integrity": "sha512-TQFM95fZ0c57/V/XO4ROBTA2cz2iE7v5Qyraz5ay6vhBsD0eP65NFEdmmdNTTE6w+YVPCMfguTaO+uBDebtj/A==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", - "@aws-sdk/middleware-sdk-api-gateway": "^3.972.13", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/middleware-sdk-api-gateway": "^3.972.14", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -222,22 +314,205 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-appsync": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-appsync/-/client-appsync-3.1039.0.tgz", + "integrity": "sha512-hH9kXgRuP3Rc7btBmVQVxA8YJLQR/FF/alW7V/RrsQMUSs+IwVlaCGJSKfbh9I2Vtb635hr+DgdyMLRuIdnB8w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1053.0.tgz", - "integrity": "sha512-optVkbzqDUlZlXlzcL/QWdC3gFteDCSPv4Yxawh1Za5MEEP1dkaEbZf7MAAbtwhtwgMTE50HvYzwMkQLYzvDqA==", - "dev": true, + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1039.0.tgz", + "integrity": "sha512-iT3IEdr2CriBy2h5LR/+O5PadxAoLNFepQ+I7pwPam77e3OhkivMUQJ/7vOfh6HGU0Mh2SENJBOO6xaAenC9fA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", - "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", - "@smithy/types": "^4.14.2", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.1039.0.tgz", + "integrity": "sha512-ew5JIGO2vb65SFXcURNy7jg2aza5p8VWWcHl/i/cawSD9/43bxxXtNkgOymU3JtCD7P7zL41Klazcc3hx3OwKA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1039.0.tgz", + "integrity": "sha512-jue1zZeTxtyCzD7vMxw5gq4XE1q8SUDoePqgdPEPPgjg5FTJsmEhpswgeLV2dP4sHkyeap4f4Pf7BCmwkgJuNQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -245,20 +520,20 @@ } }, "node_modules/@aws-sdk/client-cognito-identity-provider": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.1053.0.tgz", - "integrity": "sha512-ymcPHhNybdo0EPl1Sh/hkUbx8fvQli+FV7KYyWcuCn0cGiX59yf0JDm5kePPVwP3y/i+YXhki2fQ4xlpN6tfUQ==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.1057.0.tgz", + "integrity": "sha512-RC4A9yM3MOVUXASfp/74IDoNG4uwD7vhNkF3xxlpzm+TtAkJyGAawuCc1xmEG+IwFTuAGm1ixRy72aYLZBPdTw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -267,21 +542,21 @@ } }, "node_modules/@aws-sdk/client-eventbridge": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-eventbridge/-/client-eventbridge-3.1053.0.tgz", - "integrity": "sha512-0ziD09G2HTSd+z0khUhPphk5tkG21j4+AhnY2ADq0QuSXYRZlpzEHqCiyYOuyJtqW284NJaJH+6DSK4xOwe9mA==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-eventbridge/-/client-eventbridge-3.1057.0.tgz", + "integrity": "sha512-CE+e9+hxR0P9YxEEbvQhAOY+V8e+Znt1vM3+BKkzsA+45oZJKolR/Q2dZpcevvvz3qDb9FWL+uDH8SvXpNavSg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", - "@aws-sdk/signature-v4-multi-region": "^3.996.28", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -290,20 +565,20 @@ } }, "node_modules/@aws-sdk/client-iam": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.1053.0.tgz", - "integrity": "sha512-K8twwQta96O4Y4OUuk97n+SnA5OiPm759uR2Ffbm3a2/vQGMg2BcAQwFV+wwG9hj64+tus02AGH9dyuzRKJnKA==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.1057.0.tgz", + "integrity": "sha512-P69o/7oMiVdk50j2+nCb+Vd+SyGydD3dItfqpaa//5U1ho5DEsrS6FqK0g7TfSz9a87nrDDRh80BHs1w+B/G7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -312,20 +587,20 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1053.0.tgz", - "integrity": "sha512-rIauaabLL/2C/5GYW6r/j4ptULlsTw2D/81leZ0nrjQVu9LSuAUZBJYLJpMsQobhDon4gfkdMIObOxQY1AHhRA==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1057.0.tgz", + "integrity": "sha512-JoaE3QNvPqOSHJtcAFfza7BRoGUNerIKlSVhsKCBsa1i54Vto1YWUS7itkUELK2rpvS8kNZMPliPxDdi/oV5dw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -333,29 +608,81 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1039.0.tgz", + "integrity": "sha512-Ph6b3J8OkbhXIs/Xg3og+rG9v58gsE6qEHY5APi4nlRn6CJ1J5qRI5NWpDVE1fFR6e9dIGPuxCcGr/kN34Eo4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-sdk-route53": "^3.972.12", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-s3": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1053.0.tgz", - "integrity": "sha512-/oGxoB6p1Nqs935Blt+v1o+anSCEf2n3RjIrcLz84i4cn2Gr+Z7JpDdUkG5+74r5ctqEPG7k/phTGbJ9fNKnHg==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1057.0.tgz", + "integrity": "sha512-4MV5+ph7WSLEqStKYdWf2EIHIvLpPzV8xN98jWSVJfUpp5j7T8dyN3AROPPsKWvCme8hbx1ybCjtK76ALCZUYg==", "dev": true, "license": "Apache-2.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.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", - "@aws-sdk/middleware-bucket-endpoint": "^3.972.15", - "@aws-sdk/middleware-expect-continue": "^3.972.13", - "@aws-sdk/middleware-flexible-checksums": "^3.974.21", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.17", + "@aws-sdk/middleware-expect-continue": "^3.972.14", + "@aws-sdk/middleware-flexible-checksums": "^3.974.23", "@aws-sdk/middleware-location-constraint": "^3.972.11", - "@aws-sdk/middleware-sdk-s3": "^3.972.42", + "@aws-sdk/middleware-sdk-s3": "^3.972.44", "@aws-sdk/middleware-ssec": "^3.972.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.28", + "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -364,21 +691,21 @@ } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.1053.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.1053.0.tgz", - "integrity": "sha512-0lkXV/by8oHnKjScVfWoZsvyalkV3iiMobDYDS/vmtXnmnpHoi2jlo8Jo51vYaqTGPbI1IJ2jtHj5cLsmB7Qkg==", + "version": "3.1057.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.1057.0.tgz", + "integrity": "sha512-67Qi3j1Np/y8QAiTQn3SlYIDg6j3gUbwbjYqPzL0S0pDTYoNtnHjABrvarg21txotlQn9ZkbgBawEL3+f3A/Qg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-node": "^3.972.44", - "@aws-sdk/signature-v4-multi-region": "^3.996.28", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-node": "^3.972.47", + "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -387,17 +714,16 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.13.tgz", - "integrity": "sha512-+Y5/4tHki0uYgyx8eun146DegRVQBpdKGK5RbV0FTKJPpaKTchvqVxrrRFK6Wk0JksO4iAZKw3eqxGEIwtO98w==", - "dev": true, + "version": "3.974.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.15.tgz", + "integrity": "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.9", - "@aws-sdk/xml-builder": "^3.972.25", + "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.3", - "@smithy/signature-v4": "^5.4.2", + "@smithy/core": "^3.24.5", + "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -420,16 +746,31 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.38.tgz", + "integrity": "sha512-OHkK6xOx/IHkSbQdDWxnVCLU+j28EFl8wyWgBILQDFAPY8n240C/O4gjmFx+zFU12lL8njgJQ5GWAIWq88CnSQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/nested-clients": "^3.997.13", + "@aws-sdk/types": "^3.973.9", + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.39.tgz", - "integrity": "sha512-29wX9zpAvEt1vcj0psha+y6ygBHy2V/S72mp6e7q0KARLWXq+pwE/lR6qGkwknQvruh52lXvlqZIga8Hdxkucw==", - "dev": true, + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.41.tgz", + "integrity": "sha512-n1EbJ98yvPWWdHZZv8bRBMqqDQJrtgtxyJ4xLy2Uqrh25BCOZQ7nnS1CsFXvuH8r0b0KVHDZEGEH5FxmEMP8jg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", + "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -438,17 +779,16 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.41.tgz", - "integrity": "sha512-IA3CQTjtJkb6u1H4mE4936c8OPBMa9Jggtwe8U2Mqw/vvb/tZ5Ebd0mcZcX0uKWQhOyYo/+qNIwkV5Xh+FeJJA==", - "dev": true, + "version": "3.972.43", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.43.tgz", + "integrity": "sha512-TT76RN1NkI9WoyZqCNxOw6/WBMF7pYOTJcXbMokNFU+euSG40Kaf/t/FhDACVZWP+43wEM6ZynIPIkzS1wR1iA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", + "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -457,23 +797,22 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.43", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.43.tgz", - "integrity": "sha512-4mzII+3mZEVXXE1xzrLQrCJL7/r62A63bA6SVzZoNL5rqCJghpf+xgGltVrIBBs0n+mOZBKrQl2tRREtvZ5l6A==", - "dev": true, + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.46.tgz", + "integrity": "sha512-hvcgcwOiS0nb2XFb5Op1Pz/vYaWz5K8kKullziGpdNRuG0NwzRXseuPt2CoBqknHGaSPVesu1aOn2OcctEYdCA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/credential-provider-env": "^3.972.39", - "@aws-sdk/credential-provider-http": "^3.972.41", - "@aws-sdk/credential-provider-login": "^3.972.43", - "@aws-sdk/credential-provider-process": "^3.972.39", - "@aws-sdk/credential-provider-sso": "^3.972.43", - "@aws-sdk/credential-provider-web-identity": "^3.972.43", - "@aws-sdk/nested-clients": "^3.997.11", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/credential-provider-env": "^3.972.41", + "@aws-sdk/credential-provider-http": "^3.972.43", + "@aws-sdk/credential-provider-login": "^3.972.45", + "@aws-sdk/credential-provider-process": "^3.972.41", + "@aws-sdk/credential-provider-sso": "^3.972.45", + "@aws-sdk/credential-provider-web-identity": "^3.972.45", + "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/core": "^3.24.5", + "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -482,16 +821,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.43", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.43.tgz", - "integrity": "sha512-HG7kQCwXtbv3oBV61Ins0oNX8KKyvrMqqRkb6ZiAfQHbMuHaiNaEb2KnpKLPkNpqImSBK82UkVE/kaY6IfWikA==", - "dev": true, + "version": "3.972.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.45.tgz", + "integrity": "sha512-MZQv4SNjByk1iOKmrqmzcUF/uCB05wjvEHyXKxmGQTUANTIVayX6HPUF0bzkWLvtnkH7sAn9kUCfkXbSpj9sDA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/nested-clients": "^3.997.11", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -500,21 +838,20 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.44.tgz", - "integrity": "sha512-sDaBIT0yrNNIPfvlsiTCmANm07zKju+ipWODjEXgZlsjMeIJR3LVp7RDyAOzUoAsTbDfYKDWp+i5WrFiQP6rmQ==", - "dev": true, + "version": "3.972.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.47.tgz", + "integrity": "sha512-HrId+C0DWA5qDIyLG64/kjUB2RNtPypxmABnIctK+TA1P1kHlOYoE/Wf5T5tKOMKgb08P7k/zNyhvfJ3lh5Oag==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.39", - "@aws-sdk/credential-provider-http": "^3.972.41", - "@aws-sdk/credential-provider-ini": "^3.972.43", - "@aws-sdk/credential-provider-process": "^3.972.39", - "@aws-sdk/credential-provider-sso": "^3.972.43", - "@aws-sdk/credential-provider-web-identity": "^3.972.43", + "@aws-sdk/credential-provider-env": "^3.972.41", + "@aws-sdk/credential-provider-http": "^3.972.43", + "@aws-sdk/credential-provider-ini": "^3.972.46", + "@aws-sdk/credential-provider-process": "^3.972.41", + "@aws-sdk/credential-provider-sso": "^3.972.45", + "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/core": "^3.24.5", + "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -523,15 +860,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.39.tgz", - "integrity": "sha512-2k/amBifLd75eXNwgvPw/2lKYSQ3NhvHQgkVKVjfUq13/eJ3JRtHmznuFenn74OK3sSfp4SMy1YB2w+UVXoKqA==", - "dev": true, + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.41.tgz", + "integrity": "sha512-7I/n1zkysouLOWvkEhjNEP4vMnD2v4kzzr3/3QBdrripEpn7ap1/I5DF3Hou1SUqkKWo1f3oPGMyFAA1FAMvsQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", + "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -540,17 +876,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.43", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.43.tgz", - "integrity": "sha512-LPc3+Y4vhH1T4x6CMqwCM6hk5+SRf/Lwmgm8INm95wxTtIRHcMwQUVkDzWu4Iw/RSncxYM2BC01OrYbxOPZvyg==", - "dev": true, + "version": "3.972.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.45.tgz", + "integrity": "sha512-oHgbz/eFD8IKiksqDsz9ZMU4A59BpQq4QwJedBnGD80ZqYcHPPHZBwjBnxLVkB7iRVVHWpDclR8yWdD2PkQIUA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/nested-clients": "^3.997.11", - "@aws-sdk/token-providers": "3.1052.0", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/nested-clients": "^3.997.13", + "@aws-sdk/token-providers": "3.1056.0", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -559,16 +894,15 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.43", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.43.tgz", - "integrity": "sha512-wQtL34lUD/09VXjwAUo2T+I3aEXRDxMB3DKmTJL/Zj0Gi6sLDTrVhae1XVt01yzkquOWajI/sZW72JGDZ1ciTw==", - "dev": true, + "version": "3.972.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.45.tgz", + "integrity": "sha512-CDhzKdb2onv5bpnjn/acgdNmJOQthPDLsPizU7rZflsEcgMMp8Mlri+U5hdxf8ldvZJpvM3vLU6D56vfJm5AMQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/nested-clients": "^3.997.11", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -576,16 +910,47 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1039.0.tgz", + "integrity": "sha512-zJdE1J5p3lxk6kw40hZoI7O3aZDy3exFUWyZjjE4BGppXvfM4PQTDFRcq466JxjUc7x3X//H2oNejsH8+UErIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.1039.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.30", + "@aws-sdk/credential-provider-env": "^3.972.33", + "@aws-sdk/credential-provider-http": "^3.972.35", + "@aws-sdk/credential-provider-ini": "^3.972.37", + "@aws-sdk/credential-provider-login": "^3.972.37", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/credential-provider-process": "^3.972.33", + "@aws-sdk/credential-provider-sso": "^3.972.37", + "@aws-sdk/credential-provider-web-identity": "^3.972.37", + "@aws-sdk/nested-clients": "^3.997.5", + "@aws-sdk/types": "^3.973.8", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.15.tgz", - "integrity": "sha512-O2HDANa+MrvbxpaRVQDiH3T13uAa9AkMjKyZmDygwauAmmvqZ5B0iRmKW+fuVGW6NPXuyXurFgIx69lSvmAWGA==", + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.17.tgz", + "integrity": "sha512-lbDmWuHenc+kiwCNrxz4MyN6nkxCWyTXPIWuspJN0ibziu+8CXci7vI1bK9MAkwy8cwJOEXNu0gBM5S0uTGRIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", + "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -594,14 +959,14 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.13.tgz", - "integrity": "sha512-sHiqIFg8o2ipT7t40B89Vj0ubSUtY6OSt/+Ee/OXhHch5K4+81zP2+QX8Lkc/nJ2QSmCySxOke7TEbmX69fe2g==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.14.tgz", + "integrity": "sha512-3TNFEVGO4sWZj9TEXOCZLzGEctXHnaO4fk2EQ8KVaboTbwHmEPEQrm17Xb9koImUIXEw0sgi2xtHjg7LuTS3rA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -610,19 +975,19 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.974.21", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.21.tgz", - "integrity": "sha512-alAu9heyiBK/OmRNXVxq8mmPTgeW2AQ6EYjRsI38kPZa1MZvt2Jh+BlGq7/GG9OVXOaEgD7DlGj/Lzfy5OmuEg==", + "version": "3.974.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.23.tgz", + "integrity": "sha512-4nPKARo2lfKvQGUt2fPA5NlS/mEohckdxpuC9ecbjVfj7B7NFFYHeTg+Bf5BEQwdn3yRfUIzFiEkPp8Yuaw3wA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.974.13", + "@aws-sdk/core": "^3.974.15", "@aws-sdk/crc64-nvme": "^3.972.9", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -630,6 +995,19 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.16.tgz", + "integrity": "sha512-DcDXAl32I/YZnJ9LyX/XpRXOtoTYIwgmYxoNMGkyvtomdjPpkXPGhz93VJyzKFFNffz/SZwEkoAuWOkeOzo90Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-location-constraint": { "version": "3.972.11", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.11.tgz", @@ -645,15 +1023,55 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.15.tgz", + "integrity": "sha512-4Org8yUew+Y1+buTcH5A39unAdkVRnQxcOp3XexvFAVctbtznDytk3UKbkXq8FYWEVdz1ycxnAqHaKHePyGQEg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.17.tgz", + "integrity": "sha512-T/FpIr0OcR1kad/u5ZTDE2ziFG7QM6pq1MN8atHBmyyOJgqWjGez9TQ0W2WCixS7EE9fsUQKWn70l5M+e+O/qg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-api-gateway": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-api-gateway/-/middleware-sdk-api-gateway-3.972.13.tgz", - "integrity": "sha512-D6eILOseXLMmfw0Sqj55A/qOERHZWi9ySGOavXAuPLWUhmF80v5QeQYTs0T2f+qVU4tf0+7b6La3jiPXG6MGEQ==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-api-gateway/-/middleware-sdk-api-gateway-3.972.14.tgz", + "integrity": "sha512-OjWAOayII8w6CZP+s28lRPOiCCVxp3QXoVOsWzkO+7vJNEqqMl/tMmxXseuyt5YgzvG/XX13thBj8+EoAr5xfg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.13.tgz", + "integrity": "sha512-eCoSJ+LYrx8g8qY3aloltI2TX8c7R9tsZBdL3Xys0ULDrefGV4NLxg0IUu0xFPNxNO+M7mT2+rsPT+T2/VpfDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.9", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -662,17 +1080,16 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.42", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.42.tgz", - "integrity": "sha512-/xNqNGXv9LaxZd25L9VV4pnSOw9OdDNO4rAHamM+h3KQBSITljIH9vk3dveGga1I2j36lQd0rdG3gjNEXvtNew==", + "version": "3.972.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.44.tgz", + "integrity": "sha512-8HQsRg1NpX8vR4vNl1E8pyLnqZroq9VSL2vZQVSgBqp6wv6365LzYD08/c9FFh/9FTg7YRc7aTtEmXF0ir/pqg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.28", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/signature-v4": "^5.4.2", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -695,21 +1112,33 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.45.tgz", + "integrity": "sha512-sWd7bGH+thMsNGYdqPdLuH7SDEkpplWCDKSCWhjkMPXwz3o/9BK3ZNrd5JGUB8y+cPbs3rXIe3Ah7iSlKXj5FQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.11.tgz", - "integrity": "sha512-nWXXJ1r/r8N2Gw1pWolRgED38/A9A8DHR2ETWIv220zh4PZHcybbR4hUVWWktmNXTRHzDJwRluapHn0rZxuoqA==", - "dev": true, + "version": "3.997.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.13.tgz", + "integrity": "sha512-2pA6eyb5nSo/ZD2cayhOTEMoGQYgspq0RI05GDLkzQ3ajZ6isS6waV6E92Am/hz4LIlLUTrbwPLurJ/fuiHvkg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.28", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/fetch-http-handler": "^5.4.3", - "@smithy/node-http-handler": "^4.7.3", + "@smithy/core": "^3.24.5", + "@smithy/fetch-http-handler": "^5.4.5", + "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -717,16 +1146,27 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.19.tgz", + "integrity": "sha512-ol9yhY2nzPTrQoKX9WZ8cps2JABcDt0Dlhr59FRYYW/2S5h07PFrhfZZzD8sXZ8XpYfObTIJ+evmbcz41m1SJQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.28.tgz", - "integrity": "sha512-qs9z5LqXO/CZC2Lg9SGKpoLU8Rhi+m2pFKZqfO9pytX1clc0katqtsDNupJxFy0xT9wsZSPzM2v1y+/H/zfp5Q==", - "dev": true, + "version": "3.996.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.30.tgz", + "integrity": "sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", - "@smithy/signature-v4": "^5.4.2", + "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -735,16 +1175,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1052.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1052.0.tgz", - "integrity": "sha512-QqZNB3so7UIDxZtroc85TQaLVxdZRFm0eWM1CSR2N+b06as9TOrilvrlTZuj3guYlxMs6yLOgGxnklJ5qMYtTw==", - "dev": true, + "version": "3.1056.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1056.0.tgz", + "integrity": "sha512-81duvlltQlsfn5K+o8zILcystBRdbT1G2JJYVCML5NZHBz4CL/zf+sAemCtBh/uh6RQUMyInGeZLQ7/8igZhbA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.13", - "@aws-sdk/nested-clients": "^3.997.11", + "@aws-sdk/core": "^3.974.15", + "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", - "@smithy/core": "^3.24.3", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -756,7 +1195,6 @@ "version": "3.973.9", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.9.tgz", "integrity": "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.14.2", @@ -766,11 +1204,24 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.14.tgz", + "integrity": "sha512-9Nr9UXPnSt8k+SJKxLlv9b4VCvP/fJnjxEyumR/cN5YYFUEz6zW+C8RfAAS8JjhUGigyymlCg3tAo7+oyr/uZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.965.5", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -779,14 +1230,35 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.16.tgz", + "integrity": "sha512-h4VTGl6lxJkM/odeRPzXB5YZbkxVr5FnJ2Bwv78+IY9Ah7QsXSBhefvvz1CQDoQNzOLYsNMQ3PhMQM7i6tpoPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.31.tgz", + "integrity": "sha512-hYTWRlhOnhs2tYCYgNK30jaQKyl4FvXQl0AK9tKRZq8sunS9ygi+USYiG6LCb7DuqT0Gl3jONP9jtb4D4NYhHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.25.tgz", - "integrity": "sha512-GH+Kjz4nPKWKHnsiQpnhP1MJdTGIcK4rAka6tzakgjjUkVgNsmPeEbbRAf09SzS1hjGu6duGHCBsxYke0BhHjQ==", - "dev": true, + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.26.tgz", + "integrity": "sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==", "license": "Apache-2.0", "dependencies": { - "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.2", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" @@ -799,20 +1271,19 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -821,9 +1292,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -831,21 +1302,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -872,14 +1343,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -889,14 +1360,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -916,9 +1387,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -926,29 +1397,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -958,9 +1429,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -968,9 +1439,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -978,9 +1449,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -988,9 +1459,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -998,27 +1469,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1083,13 +1554,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1125,13 +1596,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1251,13 +1722,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1267,9 +1738,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -1277,33 +1748,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1311,14 +1782,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2624,10 +3095,9 @@ } }, "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", + "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", "funding": [ { "type": "github", @@ -2788,9 +3258,9 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.10.tgz", + "integrity": "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==", "dev": true, "license": "MIT", "dependencies": { @@ -2798,7 +3268,6 @@ "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" }, @@ -2851,13 +3320,13 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/pkgr" @@ -3466,31 +3935,6 @@ "node": ">=8.0" } }, - "node_modules/@serverless/dashboard-plugin/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/@serverless/dashboard-plugin/node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3506,17 +3950,20 @@ "node": ">= 8" } }, - "node_modules/@serverless/dashboard-plugin/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@serverless/dashboard-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/@serverless/dashboard-plugin/node_modules/open": { @@ -3546,32 +3993,6 @@ "node": ">=8" } }, - "node_modules/@serverless/dashboard-plugin/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@serverless/dashboard-plugin/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/@serverless/dashboard-plugin/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3707,17 +4128,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@serverless/platform-client/node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/@serverless/test": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/@serverless/test/-/test-11.1.1.tgz", @@ -3760,21 +4170,6 @@ } } }, - "node_modules/@serverless/test/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@serverless/typescript": { "version": "2.71.0", "resolved": "https://registry.npmjs.org/@serverless/typescript/-/typescript-2.71.0.tgz", @@ -3938,11 +4333,23 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/@smithy/config-resolver": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.5.5.tgz", + "integrity": "sha512-HehAZr4sq2m+4zHgEqDvtWENy/B5yywMKA8Pl4gBcU3F4ekelpZqDLDxQHdJlguaKNyTq31cZYjLWomzdujQrA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/core": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.4.tgz", - "integrity": "sha512-3UNRKEyQyAgVgM0LGlerCLm+ChZWZ1GPfde+jBEW6bm6bSBGU1p0EbblaUV3unbhwvidjLA5Zs3sOs7mnZwvAw==", - "dev": true, + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz", + "integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -3954,13 +4361,12 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.4.tgz", - "integrity": "sha512-vKW0MEFRU4Y3MkVZUkpJm+g9qyPGLCXhc0YLggUdSdBB4g7IaSSsCE75P9rBXyWHrXY1UYSQUl8/DwsTR7QciA==", - "dev": true, + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.6.tgz", + "integrity": "sha512-tHhdiWZfG1ZIh2YcRfPJmY2gHcBmqbAzqm3ER4TIDFYsSEqTD5tICT7cgQ/kI8LRakxp12myOYyK68XPn7MnHw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.4", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -3968,14 +4374,52 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.3.5.tgz", + "integrity": "sha512-M9rMkTar7JcRrvUHsK1271AuWDmrISIPQpQ4TSHmYZ4KMisGnMH0gfjCWnBwdndR7skvvp/UheHhZGvO3Cr8/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.4.5.tgz", + "integrity": "sha512-lUwPPu7DNNVJjeS+gV7g2rDHbW9X1wSRQIsIyzOgBtP7KDMefLhz0kz42AWAxZIFPcOO3pUbtq76LSkVcxLKRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.3.5.tgz", + "integrity": "sha512-QydEYKqvdiS6dJb0tOfDiogt12FzzImt2FnL7gMD72hNrkiUAUKqtStRmkTrdzDKFJ46abe3yH94luCuhtnCkQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.4.tgz", - "integrity": "sha512-qM7AUKI4G6d7lNgaZD3lA1tWSolh5r6gcixfTZAPstVURfjIbvreVTPz+994M0yC3HbX4YYhDRgr31Xy3XwWOQ==", - "dev": true, + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.5.tgz", + "integrity": "sha512-SK3VMeH0fibgdTg2QeB+O4p7Yy/2E5HBOHJeC58FshkDdeuX8lOgO7PfjYfLyPLP1ch55j91cQqKBzDS0mRjSQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.4", + "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, @@ -3983,88 +4427,369 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-node": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.3.5.tgz", + "integrity": "sha512-/tUIDaB36qjLq/CIhMRIiFXCT7rVGBGAhFmMA9PbC/iW2u3QPNATZuFSdK0JBO3qeSPoHBeudFMmsbFq2Mf5EQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.3.5.tgz", + "integrity": "sha512-c8C1GzrU4PcY1QT/HP0ILCTLutyVONT93kPSisOyHoZaXlKQZtV6+RKqolhBtPolGULf59vq2yseagU6+WY82w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/is-array-buffer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.3.5.tgz", + "integrity": "sha512-lzOzJ4c0t3vkBut02CjdWNgduN3mUWjc1WK9TPr75KVV6OgVWico9wMDn9ZnQN97VJPYfweBW6Dm5CElvQl8BQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.5.5.tgz", + "integrity": "sha512-8DnkSoUMQAcuT/DHdigsFPti8M/Dm6TPCAsrIQ/bUDGxRkrgGuI++3dXRr8CoUyc9r0kGSCcZHjJje407ydgBQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.6.5.tgz", + "integrity": "sha512-fumMIfh5xOFjirylbSzmBX9bgQtrWFtQrosPfkjsJSBzqXVbQMNDGIC8oJBz4V3bokIm2F0CL3bziLtbXR7cbA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.3.5.tgz", + "integrity": "sha512-+7glfRrb7byruZCPAM53TvmK8cx/ghzAThB4EvPzHynAYobtISl0g+DzzSVEC0NQob5BunP9gC9GP+Fcz6H9yw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.3.5.tgz", + "integrity": "sha512-Yj4wjBQZXHePRIy9cBIKfCOn/kPjRlgDPGlr7DjIhwrnz8kWu7Ux7UwPr51P/wcug5oq4nWdBXSY4TV5afBdew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.4.5.tgz", + "integrity": "sha512-c2G9QJ4xVZLwAkAf+WQESSSCkKbtt33ytje1klGvTcBn6cKuqV28E+62wbRPHwuTikkB3LQ7CBnNrayCoJur5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.5.tgz", + "integrity": "sha512-3dA9TQ+ybRSZ/m0wnbZhiBy4Dezjgq1Ib/ZZrYTpJDBgpoLLU/SDzZc/g0x0MNAdOJe1wPcM+x2PBRmoOur+Sw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.3.5.tgz", + "integrity": "sha512-QNc22/FgfEm/9/rkefShfQUVckH3HWiQ2RPs+40hwAdY65hbg88gombeHwkfMzmVDZjolcyQeyOjnxZRmpavIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.4.5.tgz", + "integrity": "sha512-jOD+4WNWQLntiLJn3r82C7BLheEbRCKTbU5U5bskZmT7nwRiGkh0IghuHwHRZ1ZEFXpHltQxxp9/koOPsdluJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.5.tgz", + "integrity": "sha512-QBJKWGqIknH0dc9LWpfH1mkdokAx6iXYN3UcQ3eY6uIEyScuoQAhfl94ge7ozUy9WgFUdE8xsvwBjaYBbWmPNA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.13.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.13.5.tgz", + "integrity": "sha512-pg9QRQESz3m/5HgAW/z9lA3ln8MSsCWNWc82MX40Djlxpcj/+7DZQ0yIk7tGWYJCVZog/9LBdNl1uEVRAhqm5Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.3.5.tgz", + "integrity": "sha512-f7kUYrRdLiAHz10WXQXiUkuBFaL2c2ZBD2kSwZyQBh73lWFTvXwdpS9l5irQ/uldk8YMJpm66BozmqCg/3uZvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.4.5.tgz", + "integrity": "sha512-2J8l+DoX3IIiP75X5SYkJ3mIgOkxW29MxOs7oPjbXLuInQ7UL6zLw2IJHbQ44+eKDBBhTjvt+GgwsTTNBGt8zA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.3.5.tgz", + "integrity": "sha512-nQtYwXg4spM6uc0Luq3yck+WXZ1VPfrYkC2SqkQ+YOGks0qR2bKKlSCjidSqfpq+VAY/RJe1O5V+CtBmnT63KQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.3.5.tgz", + "integrity": "sha512-BAsAed9yWExECwNIi61Le6D8ZTY71MFEFrf3d4L2+uzcbTjFAWxOtymkA1vCV8bNZQN9TGgZo4c68JDsnjNShA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.4.5.tgz", + "integrity": "sha512-LbE6AGHhQOunqIN5UyWDMgpPwmUHUzrV2NtUOQ+lt6Stpipzo6S7uDyeGtO0GGgUD1balEPCNu8Xfl1AQNiruQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.3.5.tgz", + "integrity": "sha512-72gNpNDQ2iIGbaNmeaF9I58shWsEuD5tNI7my5uXlm1CSPH5i8IKI/nzU50qqB8y+kgw/qTLGgsf0We5qeM/aA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.5.5.tgz", + "integrity": "sha512-NJhe8KmNjeZ7V+gJsQR5xw0IN47N8pBKosed40xfhelDuYkg8VQ5CVGDcHTEuJq3e3zQb21vnoOOReQothejhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.4.tgz", - "integrity": "sha512-HIeF+1vrDGzPkkv39Hj2vlHSXHY3p958jd/8ZnePIY6+ZOsQX8coyEUKO5yQu4r0bQIVsbpotVIrXXwyycMStQ==", - "dev": true, + "node_modules/@smithy/util-middleware": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.3.5.tgz", + "integrity": "sha512-N1IR4bMHIDbqO3GxkJHgqNGsnrd7MNrj+EVqhFqKeRqSBV5I3KCjNllKfnbF9KV0YteGhfLqcMR5CYsPLJqpqw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.4", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/signature-v4": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.4.tgz", - "integrity": "sha512-e5UtkMvsatzBfbeBZjEOt0k0Z3BEsjTFL/n6fdO5vtBLe67tdy0dX7xw2DU7uZ3acwoHyeCqpU2Fzb7pxwHb6Q==", - "dev": true, + "node_modules/@smithy/util-retry": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.4.5.tgz", + "integrity": "sha512-W9Ovy9i02yGqtLlpqZNQuXNxXc5OPfXujnembxN/FxyBtGjJd8vKY0PQYEJ8FNybTOcXG+ZxsSsX23HOb3zQzg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.4", - "@smithy/types": "^4.14.2", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", - "dev": true, + "node_modules/@smithy/util-stream": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.6.5.tgz", + "integrity": "sha512-PFzBVEBP5k8R+mK/c+VAKmtpUTL+KzBIXWJ6oM0GWOb31K+QgymXV9IW03XLPM1wtkC7oAb9ZBN2aswSSVbNFg==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, + "node_modules/@smithy/util-utf8": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.5.tgz", + "integrity": "sha512-l1d7I7YP2LjXjAZDC7eXqkzuEB75KfCANwhNj/knmT6+0a9XG3QasvI8kEn8WAI3tx/q8PdmSuuXcM+MTkk/7Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, + "node_modules/@smithy/util-waiter": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.4.5.tgz", + "integrity": "sha512-EYviebytZE6vplW0AGwZ2Rc3sNuVR83lfUCNZu11VchUiKhMwJqrRWy7iVDTNEwG/vEwItno591Iad6/prj6Bw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@szmarczak/http-timer": { @@ -5227,6 +5952,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/archiver-utils/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5496,6 +6228,7 @@ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1693.0.tgz", "integrity": "sha512-cJmb8xEnVLT+R6fBS5sn/EFJiX7tUnDaPtOPZ1vFbOJtd0fnZn/Ky2XGgsvvoeliWeH7mL3TWSX5zXXGSQV6gQ==", "deprecated": "The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -5514,11 +6247,48 @@ "node": ">= 10.0.0" } }, + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/aws-sdk/node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-sdk/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-sdk/node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/aws-sdk/node_modules/uuid": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -5691,9 +6461,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", - "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5734,30 +6504,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -5776,13 +6522,12 @@ "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -5860,14 +6605,27 @@ } }, "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/buffer-alloc": { @@ -6125,6 +6883,44 @@ "stream-promise": "^3.2.0" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -6295,6 +7091,16 @@ "node": ">=10" } }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/cli-progress-footer": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-2.3.3.tgz", @@ -6907,9 +7713,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "dev": true, "license": "MIT" }, @@ -7387,6 +8193,13 @@ "readable-stream": "^2.0.2" } }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/duplexer2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -7438,9 +8251,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.361", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", - "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", "dev": true, "license": "ISC" }, @@ -8089,9 +8902,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", + "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8500,6 +9313,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.x" @@ -8710,22 +9524,28 @@ "node": ">=0.10.0" } }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" }, { "type": "opencollective", - "url": "https://opencollective.com/fastify" + "url": "https://opencollective.com/fast-check" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -8815,7 +9635,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "dev": true, "funding": [ { "type": "github", @@ -8832,7 +9651,6 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "dev": true, "funding": [ { "type": "github", @@ -9270,19 +10088,18 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/fs-minipass": { @@ -9416,6 +10233,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9605,9 +10423,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -9852,9 +10670,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9900,9 +10718,9 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", - "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -10006,9 +10824,23 @@ } }, "node_modules/ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "BSD-3-Clause" }, "node_modules/ignore": { @@ -10194,6 +11026,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -10430,6 +11263,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.4", @@ -10563,6 +11397,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -10736,9 +11571,9 @@ } }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "license": "MIT" }, "node_modules/isexe": { @@ -10959,6 +11794,23 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-circus/node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/jest-cli": { "version": "30.4.2", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", @@ -11054,16 +11906,6 @@ "node": ">=12" } }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "30.4.2", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", @@ -11557,6 +12399,7 @@ "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.6.0" @@ -11853,6 +12696,13 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jszip/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -11921,6 +12771,13 @@ "node": ">= 0.6.3" } }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -12326,6 +13183,22 @@ "log": "^6.0.0" } }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -13231,9 +14104,9 @@ } }, "node_modules/npm": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.15.0.tgz", - "integrity": "sha512-+k0tk7lRnpMUPnC7kTuU/yrV/mnFoPhJQ75VfLtZ6fwbzOVXaPsTE/Il9Pn1DHi482byMyqkHv/XsQ76mNjXLw==", + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.16.0.tgz", + "integrity": "sha512-A74XL8OxmcegZDMWPkWb5bEQppg8HdYwW3rBD2sPoS4UQHVajfaxBkqyzLeJ3wR0kZ+5xoTjItxXaF7eIXUsyw==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -13312,8 +14185,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.6.0", - "@npmcli/config": "^10.9.1", + "@npmcli/arborist": "^9.7.0", + "@npmcli/config": "^10.10.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -13337,16 +14210,16 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.8", - "libnpmexec": "^10.2.8", - "libnpmfund": "^7.0.22", + "libnpmdiff": "^8.1.9", + "libnpmexec": "^10.2.9", + "libnpmfund": "^7.0.23", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.8", + "libnpmpack": "^9.1.9", "libnpmpublish": "^11.2.0", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", - "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.5", + "libnpmversion": "^8.0.4", + "make-fetch-happen": "^15.0.6", "minimatch": "^10.2.5", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", @@ -13366,7 +14239,7 @@ "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.8.0", + "semver": "^7.8.1", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", @@ -13455,7 +14328,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "4.0.0", + "version": "4.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -13471,7 +14344,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.6.0", + "version": "9.7.0", "dev": true, "inBundle": true, "license": "ISC", @@ -13519,7 +14392,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.9.1", + "version": "10.10.0", "dev": true, "inBundle": true, "license": "ISC", @@ -13713,7 +14586,7 @@ } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.2.0", + "version": "3.2.1", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -13761,13 +14634,13 @@ } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "3.1.0", + "version": "3.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { @@ -14238,12 +15111,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.8", + "version": "8.1.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.6.0", + "@npmcli/arborist": "^9.7.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -14257,13 +15130,13 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.2.8", + "version": "10.2.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.6.0", + "@npmcli/arborist": "^9.7.0", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", @@ -14280,12 +15153,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.22", + "version": "7.0.23", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.6.0" + "@npmcli/arborist": "^9.7.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -14305,12 +15178,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.8", + "version": "9.1.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.6.0", + "@npmcli/arborist": "^9.7.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -14364,7 +15237,7 @@ } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "8.0.3", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "ISC", @@ -14380,7 +15253,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.5.0", + "version": "11.5.1", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -14389,7 +15262,7 @@ } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "15.0.5", + "version": "15.0.6", "dev": true, "inBundle": true, "license": "ISC", @@ -14890,7 +15763,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.8.0", + "version": "7.8.1", "dev": true, "inBundle": true, "license": "ISC", @@ -14914,17 +15787,17 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "4.1.0", + "version": "4.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.0", - "@sigstore/tuf": "^4.0.1", - "@sigstore/verify": "^3.1.0" + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -15111,7 +15984,7 @@ } }, "node_modules/npm/node_modules/undici": { - "version": "6.25.0", + "version": "6.26.0", "dev": true, "inBundle": true, "license": "MIT", @@ -15393,22 +16266,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -15676,7 +16533,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, "funding": [ { "type": "github", @@ -16196,9 +17052,9 @@ } }, "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -16229,10 +17085,12 @@ } }, "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.x" } @@ -16475,26 +17333,6 @@ "node": ">=0.8.x" } }, - "node_modules/readable-web-to-node-stream/node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -16522,9 +17360,9 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -16544,6 +17382,32 @@ "node": ">=10" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -16862,13 +17726,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -16906,17 +17763,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -16940,6 +17791,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "dev": true, "license": "ISC" }, "node_modules/seek-bzip": { @@ -17498,21 +18350,6 @@ "node": ">= 6.0.0" } }, - "node_modules/serverless/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/serverless/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -18127,6 +18964,13 @@ "readable-stream": "^2.0.2" } }, + "node_modules/stream-combiner2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stream-combiner2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -18406,7 +19250,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "dev": true, "funding": [ { "type": "github", @@ -18546,13 +19389,13 @@ } }, "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.9" + "@pkgr/core": "^0.3.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -18621,6 +19464,12 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/tar-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/tar-stream/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -18855,6 +19704,13 @@ "xtend": "~4.0.1" } }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -18918,9 +19774,9 @@ } }, "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.3.tgz", + "integrity": "sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==", "dev": true, "license": "MIT", "engines": { @@ -18928,9 +19784,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -18978,12 +19834,6 @@ "node": ">= 0.4" } }, - "node_modules/to-buffer/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -19013,26 +19863,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/token-types/node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -19139,16 +19969,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -19377,18 +20197,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -19454,34 +20274,10 @@ "through": "^2.3.8" } }, - "node_modules/unbzip2-stream/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", "dev": true, "license": "MIT", "engines": { @@ -19652,6 +20448,7 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dev": true, "license": "MIT", "dependencies": { "punycode": "1.3.2", @@ -19672,12 +20469,24 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true, "license": "MIT" }, + "node_modules/url/node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -19849,13 +20658,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/which-collection": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", @@ -19883,13 +20685,13 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -20004,7 +20806,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "dev": true, "funding": [ { "type": "github", @@ -20020,6 +20821,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -20033,6 +20835,7 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4.0" @@ -20155,13 +20958,13 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs/node_modules/find-up": { diff --git a/package.json b/package.json index 953f975d..7f54e420 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", + "fast-check": "^3.23.2", "husky": "^9.1.7", "jest": "^30.4.2", "lint-staged": "^16.2.3", @@ -54,13 +55,18 @@ } }, "dependencies": { + "@aws-sdk/client-acm": "3.1039.0", + "@aws-sdk/client-appsync": "3.1039.0", + "@aws-sdk/client-cloudformation": "3.1039.0", + "@aws-sdk/client-cloudwatch-logs": "3.1039.0", + "@aws-sdk/client-route-53": "3.1039.0", + "@aws-sdk/credential-providers": "3.1039.0", "@graphql-tools/merge": "^8.3.12", "@serverless/utils": "^6.8.2", "ajv": "^8.11.2", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "ajv-merge-patch": "^5.0.1", - "aws-sdk": "^2.1265.0", "chalk": "^4.1.2", "esbuild": "^0.17.11", "globby": "^11.1.0", diff --git a/src/__tests__/aws-client-factory.pbt.test.ts b/src/__tests__/aws-client-factory.pbt.test.ts new file mode 100644 index 00000000..3700b162 --- /dev/null +++ b/src/__tests__/aws-client-factory.pbt.test.ts @@ -0,0 +1,170 @@ +/** + * Property-based tests for AwsClientFactory (Properties 1 & 2). + * Tests the real AwsClientFactory with mocked SDK clients. + * + * Feature: aws-sdk-v3-migration + */ + +import * as fc from 'fast-check'; +import { AppSyncClient } from '@aws-sdk/client-appsync'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'; +import { Route53Client } from '@aws-sdk/client-route-53'; +import { ACMClient } from '@aws-sdk/client-acm'; +import { AwsClientFactory, AwsCredentials } from '../aws-client-factory'; + +// Mock the SDK clients so no real AWS calls are made. +// The real AwsClientFactory is NOT mocked — we test it directly. +jest.mock('@aws-sdk/client-appsync'); +jest.mock('@aws-sdk/client-cloudformation'); +jest.mock('@aws-sdk/client-cloudwatch-logs'); +jest.mock('@aws-sdk/client-route-53'); +jest.mock('@aws-sdk/client-acm'); + +const mockAppSyncClient = AppSyncClient as jest.MockedClass< + typeof AppSyncClient +>; +const mockCloudFormationClient = CloudFormationClient as jest.MockedClass< + typeof CloudFormationClient +>; +const mockCloudWatchLogsClient = CloudWatchLogsClient as jest.MockedClass< + typeof CloudWatchLogsClient +>; +const mockRoute53Client = Route53Client as jest.MockedClass< + typeof Route53Client +>; +const mockAcmClient = ACMClient as jest.MockedClass; + +const STATIC_CREDENTIALS: AwsCredentials = { + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', +}; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +// --------------------------------------------------------------------------- +// Property 1: Region propagation to SDK v3 clients +// Feature: aws-sdk-v3-migration, Property 1: Region propagation to SDK v3 clients +// --------------------------------------------------------------------------- +describe('Property 1: Region propagation to SDK v3 clients', () => { + it('AppSyncClient is constructed with the exact region passed to AwsClientFactory', () => { + fc.assert( + fc.property(fc.string({ minLength: 1 }), (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getAppSyncClient(); + expect(mockAppSyncClient).toHaveBeenCalledWith( + expect.objectContaining({ region }), + ); + }), + { numRuns: 100 }, + ); + }); + + it('CloudFormationClient is constructed with the exact region passed to AwsClientFactory', () => { + fc.assert( + fc.property(fc.string({ minLength: 1 }), (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getCloudFormationClient(); + expect(mockCloudFormationClient).toHaveBeenCalledWith( + expect.objectContaining({ region }), + ); + }), + { numRuns: 100 }, + ); + }); + + it('CloudWatchLogsClient is constructed with the exact region passed to AwsClientFactory', () => { + fc.assert( + fc.property(fc.string({ minLength: 1 }), (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getCloudWatchLogsClient(); + expect(mockCloudWatchLogsClient).toHaveBeenCalledWith( + expect.objectContaining({ region }), + ); + }), + { numRuns: 100 }, + ); + }); + + it('Route53Client is constructed with the exact region passed to AwsClientFactory', () => { + fc.assert( + fc.property(fc.string({ minLength: 1 }), (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getRoute53Client(); + expect(mockRoute53Client).toHaveBeenCalledWith( + expect.objectContaining({ region }), + ); + }), + { numRuns: 100 }, + ); + }); +}); + +// --------------------------------------------------------------------------- +// Property 2: ACM client always uses us-east-1 +// Feature: aws-sdk-v3-migration, Property 2: ACM client always uses us-east-1 +// --------------------------------------------------------------------------- +describe('Property 2: ACM client always uses us-east-1', () => { + it('ACMClient is always constructed with us-east-1 regardless of the region passed to AwsClientFactory', () => { + fc.assert( + fc.property(fc.string({ minLength: 1 }), (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getAcmClient(); + expect(mockAcmClient).toHaveBeenCalledWith( + expect.objectContaining({ region: 'us-east-1' }), + ); + }), + { numRuns: 100 }, + ); + }); + + it('ACMClient is never constructed with the region passed to AwsClientFactory when it differs from us-east-1', () => { + fc.assert( + fc.property( + fc.string({ minLength: 1 }).filter((r) => r !== 'us-east-1'), + (region) => { + jest.clearAllMocks(); + const factory = new AwsClientFactory(region, STATIC_CREDENTIALS); + factory.getAcmClient(); + expect(mockAcmClient).not.toHaveBeenCalledWith( + expect.objectContaining({ region }), + ); + }, + ), + { numRuns: 100 }, + ); + }); +}); + +// --------------------------------------------------------------------------- +// Property 5: Introspection schema Uint8Array round-trip +// Feature: aws-sdk-v3-migration, Property 5: Introspection schema Uint8Array round-trip +// --------------------------------------------------------------------------- +describe('Property 5: Introspection schema Uint8Array round-trip', () => { + it('Buffer.from(schema).toString() returns the original schema string unchanged', () => { + fc.assert( + fc.property(fc.string(), (schema) => { + const roundTripped = Buffer.from(schema).toString(); + expect(roundTripped).toBe(schema); + }), + { numRuns: 100 }, + ); + }); + + it('TextDecoder.decode(Buffer.from(schema)) returns the original schema string unchanged', () => { + fc.assert( + fc.property(fc.string(), (schema) => { + const decoded = new TextDecoder().decode(Buffer.from(schema)); + expect(decoded).toBe(schema); + }), + { numRuns: 100 }, + ); + }); +}); diff --git a/src/__tests__/aws-client-factory.test.ts b/src/__tests__/aws-client-factory.test.ts new file mode 100644 index 00000000..879aa26c --- /dev/null +++ b/src/__tests__/aws-client-factory.test.ts @@ -0,0 +1,200 @@ +import { AppSyncClient } from '@aws-sdk/client-appsync'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'; +import { Route53Client } from '@aws-sdk/client-route-53'; +import { ACMClient } from '@aws-sdk/client-acm'; +import { AwsClientFactory, AwsCredentials } from '../aws-client-factory'; + +jest.mock('@aws-sdk/client-appsync'); +jest.mock('@aws-sdk/client-cloudformation'); +jest.mock('@aws-sdk/client-cloudwatch-logs'); +jest.mock('@aws-sdk/client-route-53'); +jest.mock('@aws-sdk/client-acm'); + +const mockAppSyncClient = AppSyncClient as jest.MockedClass< + typeof AppSyncClient +>; +const mockCloudFormationClient = CloudFormationClient as jest.MockedClass< + typeof CloudFormationClient +>; +const mockCloudWatchLogsClient = CloudWatchLogsClient as jest.MockedClass< + typeof CloudWatchLogsClient +>; +const mockRoute53Client = Route53Client as jest.MockedClass< + typeof Route53Client +>; +const mockAcmClient = ACMClient as jest.MockedClass; + +const TEST_REGION = 'eu-west-1'; +const TEST_CREDENTIALS: AwsCredentials = { + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + sessionToken: 'test-session-token', +}; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('AwsClientFactory', () => { + describe('region configuration', () => { + it('creates AppSyncClient with the region passed to the constructor', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getAppSyncClient(); + expect(mockAppSyncClient).toHaveBeenCalledWith( + expect.objectContaining({ region: TEST_REGION }), + ); + }); + + it('creates CloudFormationClient with the region passed to the constructor', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getCloudFormationClient(); + expect(mockCloudFormationClient).toHaveBeenCalledWith( + expect.objectContaining({ region: TEST_REGION }), + ); + }); + + it('creates CloudWatchLogsClient with the region passed to the constructor', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getCloudWatchLogsClient(); + expect(mockCloudWatchLogsClient).toHaveBeenCalledWith( + expect.objectContaining({ region: TEST_REGION }), + ); + }); + + it('creates Route53Client with the region passed to the constructor', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getRoute53Client(); + expect(mockRoute53Client).toHaveBeenCalledWith( + expect.objectContaining({ region: TEST_REGION }), + ); + }); + + it('creates ACMClient with us-east-1 regardless of the region passed to the constructor', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getAcmClient(); + expect(mockAcmClient).toHaveBeenCalledWith( + expect.objectContaining({ region: 'us-east-1' }), + ); + }); + + it('does not pass the constructor region to ACMClient', () => { + const factory = new AwsClientFactory('ap-southeast-1', TEST_CREDENTIALS); + factory.getAcmClient(); + expect(mockAcmClient).not.toHaveBeenCalledWith( + expect.objectContaining({ region: 'ap-southeast-1' }), + ); + }); + }); + + describe('lazy initialization', () => { + it('returns the same AppSyncClient instance on repeated calls', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + const first = factory.getAppSyncClient(); + const second = factory.getAppSyncClient(); + expect(first).toBe(second); + expect(mockAppSyncClient).toHaveBeenCalledTimes(1); + }); + + it('returns the same CloudFormationClient instance on repeated calls', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + const first = factory.getCloudFormationClient(); + const second = factory.getCloudFormationClient(); + expect(first).toBe(second); + expect(mockCloudFormationClient).toHaveBeenCalledTimes(1); + }); + + it('returns the same CloudWatchLogsClient instance on repeated calls', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + const first = factory.getCloudWatchLogsClient(); + const second = factory.getCloudWatchLogsClient(); + expect(first).toBe(second); + expect(mockCloudWatchLogsClient).toHaveBeenCalledTimes(1); + }); + + it('returns the same Route53Client instance on repeated calls', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + const first = factory.getRoute53Client(); + const second = factory.getRoute53Client(); + expect(first).toBe(second); + expect(mockRoute53Client).toHaveBeenCalledTimes(1); + }); + + it('returns the same ACMClient instance on repeated calls', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + const first = factory.getAcmClient(); + const second = factory.getAcmClient(); + expect(first).toBe(second); + expect(mockAcmClient).toHaveBeenCalledTimes(1); + }); + }); + + describe('credentials forwarding', () => { + it('passes static credentials to AppSyncClient', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getAppSyncClient(); + expect(mockAppSyncClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: TEST_CREDENTIALS }), + ); + }); + + it('passes static credentials to CloudFormationClient', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getCloudFormationClient(); + expect(mockCloudFormationClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: TEST_CREDENTIALS }), + ); + }); + + it('passes static credentials to CloudWatchLogsClient', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getCloudWatchLogsClient(); + expect(mockCloudWatchLogsClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: TEST_CREDENTIALS }), + ); + }); + + it('passes static credentials to Route53Client', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getRoute53Client(); + expect(mockRoute53Client).toHaveBeenCalledWith( + expect.objectContaining({ credentials: TEST_CREDENTIALS }), + ); + }); + + it('passes static credentials to ACMClient', () => { + const factory = new AwsClientFactory(TEST_REGION, TEST_CREDENTIALS); + factory.getAcmClient(); + expect(mockAcmClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: TEST_CREDENTIALS }), + ); + }); + + it('passes a CredentialProvider function to all clients', () => { + const credentialProvider = jest.fn(); + const factory = new AwsClientFactory(TEST_REGION, credentialProvider); + + factory.getAppSyncClient(); + factory.getCloudFormationClient(); + factory.getCloudWatchLogsClient(); + factory.getRoute53Client(); + factory.getAcmClient(); + + expect(mockAppSyncClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: credentialProvider }), + ); + expect(mockCloudFormationClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: credentialProvider }), + ); + expect(mockCloudWatchLogsClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: credentialProvider }), + ); + expect(mockRoute53Client).toHaveBeenCalledWith( + expect.objectContaining({ credentials: credentialProvider }), + ); + expect(mockAcmClient).toHaveBeenCalledWith( + expect.objectContaining({ credentials: credentialProvider }), + ); + }); + }); +}); diff --git a/src/__tests__/commands.test.ts b/src/__tests__/commands.test.ts index 2c4bea5a..099876dd 100644 --- a/src/__tests__/commands.test.ts +++ b/src/__tests__/commands.test.ts @@ -1,42 +1,69 @@ import { runServerless } from './utils'; +import { plugin } from './given'; import * as utils from '../utils'; -import ServerlessError from 'serverless/lib/serverless-error'; +import { + CreateDomainNameCommand, + DeleteDomainNameCommand, + GetApiAssociationCommand, + AssociateApiCommand, + DisassociateApiCommand, + GetDomainNameCommand, + EvaluateCodeCommand, + EvaluateMappingTemplateCommand, + GetGraphqlApiEnvironmentVariablesCommand, + PutGraphqlApiEnvironmentVariablesCommand, +} from '@aws-sdk/client-appsync'; +import { DescribeStackResourcesCommand } from '@aws-sdk/client-cloudformation'; +import { + ListHostedZonesByNameCommand, + ChangeResourceRecordSetsCommand, + GetChangeCommand, +} from '@aws-sdk/client-route-53'; +import { ListCertificatesCommand } from '@aws-sdk/client-acm'; + +// Mock AwsClientFactory so no real AWS credentials or SDK calls are made +const mockSend = jest.fn(); +jest.mock('../aws-client-factory', () => ({ + AwsClientFactory: jest.fn().mockImplementation(() => ({ + getAppSyncClient: () => ({ send: mockSend }), + getCloudFormationClient: () => ({ send: mockSend }), + getCloudWatchLogsClient: () => ({ send: mockSend }), + getRoute53Client: () => ({ send: mockSend }), + getAcmClient: () => ({ send: mockSend }), + })), +})); + +// Mock fromNodeProviderChain to avoid ESM dynamic import issues in Jest +jest.mock('@aws-sdk/credential-providers', () => ({ + fromNodeProviderChain: jest.fn().mockReturnValue({}), +})); jest.setTimeout(30000); const confirmSpy = jest.spyOn(utils, 'confirmAction'); -const describeStackResources = jest.fn().mockResolvedValue({ + +// Default mockSend implementation: returns describeStackResources response for CF calls. +// Individual tests override this via mockSend.mockImplementation or mockResolvedValueOnce. +const describeStackResourcesResponse = { StackResources: [ { ResourceType: 'AWS::AppSync::GraphQLApi', PhysicalResourceId: 'appSyync/123456789', }, ], -}); +}; afterEach(() => { - describeStackResources.mockClear(); + mockSend.mockReset(); confirmSpy.mockClear(); }); describe('create domain', () => { - const createDomainName = jest.fn(); - const listCertificates = jest.fn(); - afterEach(() => { - createDomainName.mockClear(); - listCertificates.mockClear(); - }); it('should create a domain with specified certificate ARN', async () => { + mockSend.mockResolvedValue({}); + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - createDomainName, - }, - ACM: { - listCertificates, - }, - }, command: 'appsync domain create', configExt: { appSync: { @@ -49,9 +76,16 @@ describe('create domain', () => { }, }); - expect(createDomainName).toHaveBeenCalledTimes(1); - expect(listCertificates).not.toHaveBeenCalled(); - expect(createDomainName.mock.calls[0][0]).toMatchInlineSnapshot(` + const createCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof CreateDomainNameCommand, + ); + const listCertCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListCertificatesCommand, + ); + + expect(createCall).toBeDefined(); + expect(listCertCall).toBeUndefined(); + expect(createCall![0].input).toMatchInlineSnapshot(` { "certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/8acd9c69-1704-462c-be91-b5d7ce45c493", "domainName": "api.example.com", @@ -60,36 +94,33 @@ describe('create domain', () => { }); it('should create a domain and find a matching certificate, exact match', async () => { - listCertificates.mockResolvedValueOnce({ - CertificateSummaryList: [ - { - DomainName: '*.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/fd8f67f7-bf19-4894-80db-0c49bf5dd507', - }, - { - DomainName: 'foo.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', - }, - { - DomainName: 'api.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/8acd9c69-1704-462c-be91-b5d7ce45c493', - }, - ], + mockSend.mockImplementation((cmd) => { + if (cmd instanceof ListCertificatesCommand) { + return Promise.resolve({ + CertificateSummaryList: [ + { + DomainName: '*.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/fd8f67f7-bf19-4894-80db-0c49bf5dd507', + }, + { + DomainName: 'foo.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', + }, + { + DomainName: 'api.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/8acd9c69-1704-462c-be91-b5d7ce45c493', + }, + ], + }); + } + return Promise.resolve({}); }); await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - createDomainName, - }, - ACM: { - listCertificates, - }, - }, command: 'appsync domain create', configExt: { appSync: { @@ -100,16 +131,23 @@ describe('create domain', () => { }, }); - expect(listCertificates).toHaveBeenCalledTimes(1); - expect(listCertificates.mock.calls[0][0]).toMatchInlineSnapshot(` + const listCertCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof ListCertificatesCommand, + ); + const createCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof CreateDomainNameCommand, + ); + + expect(listCertCalls).toHaveLength(1); + expect(listCertCalls[0][0].input).toMatchInlineSnapshot(` { "CertificateStatuses": [ "ISSUED", ], } `); - expect(createDomainName).toHaveBeenCalledTimes(1); - expect(createDomainName.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(createCall).toBeDefined(); + expect(createCall![0].input).toMatchInlineSnapshot(` { "certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/8acd9c69-1704-462c-be91-b5d7ce45c493", "domainName": "api.example.com", @@ -118,29 +156,24 @@ describe('create domain', () => { }); it('should fail creating a domain if ARN cannot be resolved', async () => { - listCertificates.mockResolvedValueOnce({ - CertificateSummaryList: [ - { - DomainName: 'foo.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', - }, - ], + mockSend.mockImplementation((cmd) => { + if (cmd instanceof ListCertificatesCommand) { + return Promise.resolve({ + CertificateSummaryList: [ + { + DomainName: 'foo.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', + }, + ], + }); + } + return Promise.resolve({}); }); await expect( runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - createDomainName, - }, - - ACM: { - listCertificates, - }, - }, - command: 'appsync domain create', configExt: { appSync: { @@ -154,43 +187,47 @@ describe('create domain', () => { `"No certificate found for domain api.example.com."`, ); - expect(listCertificates).toHaveBeenCalledTimes(1); - expect(listCertificates.mock.calls[0][0]).toMatchInlineSnapshot(` + const listCertCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof ListCertificatesCommand, + ); + const createCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof CreateDomainNameCommand, + ); + + expect(listCertCalls).toHaveLength(1); + expect(listCertCalls[0][0].input).toMatchInlineSnapshot(` { "CertificateStatuses": [ "ISSUED", ], } `); - expect(createDomainName).not.toHaveBeenCalled(); + expect(createCall).toBeUndefined(); }); it('should create a domain and find a matching certificate, wildcard match', async () => { - listCertificates.mockResolvedValueOnce({ - CertificateSummaryList: [ - { - DomainName: 'foo.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', - }, - { - DomainName: '*.example.com', - CertificateArn: - 'arn:aws:acm:us-east-1:123456789012:certificate/fd8f67f7-bf19-4894-80db-0c49bf5dd507', - }, - ], + mockSend.mockImplementation((cmd) => { + if (cmd instanceof ListCertificatesCommand) { + return Promise.resolve({ + CertificateSummaryList: [ + { + DomainName: 'foo.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/932b56de-bb63-45fe-8a31-b3150fb9accd', + }, + { + DomainName: '*.example.com', + CertificateArn: + 'arn:aws:acm:us-east-1:123456789012:certificate/fd8f67f7-bf19-4894-80db-0c49bf5dd507', + }, + ], + }); + } + return Promise.resolve({}); }); await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - createDomainName, - }, - ACM: { - listCertificates, - }, - }, command: 'appsync domain create', configExt: { appSync: { @@ -201,16 +238,23 @@ describe('create domain', () => { }, }); - expect(listCertificates).toHaveBeenCalledTimes(1); - expect(listCertificates.mock.calls[0][0]).toMatchInlineSnapshot(` + const listCertCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof ListCertificatesCommand, + ); + const createCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof CreateDomainNameCommand, + ); + + expect(listCertCalls).toHaveLength(1); + expect(listCertCalls[0][0].input).toMatchInlineSnapshot(` { "CertificateStatuses": [ "ISSUED", ], } `); - expect(createDomainName).toHaveBeenCalledTimes(1); - expect(createDomainName.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(createCall).toBeDefined(); + expect(createCall![0].input).toMatchInlineSnapshot(` { "certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/fd8f67f7-bf19-4894-80db-0c49bf5dd507", "domainName": "api.example.com", @@ -220,20 +264,12 @@ describe('create domain', () => { }); describe('delete domain', () => { - const deleteDomainName = jest.fn(); - afterEach(() => { - deleteDomainName.mockClear(); - }); it('should delete a domain, asking for confirmation', async () => { confirmSpy.mockResolvedValue(true); + mockSend.mockResolvedValue({}); await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - deleteDomainName, - }, - }, command: 'appsync domain delete', configExt: { appSync: { @@ -244,8 +280,11 @@ describe('delete domain', () => { }, }); - expect(deleteDomainName).toHaveBeenCalledTimes(1); - expect(deleteDomainName.mock.calls[0][0]).toMatchInlineSnapshot(` + const deleteCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DeleteDomainNameCommand, + ); + expect(deleteCall).toBeDefined(); + expect(deleteCall![0].input).toMatchInlineSnapshot(` { "domainName": "api.example.com", } @@ -255,14 +294,10 @@ describe('delete domain', () => { it('should delete a domain, skipping confirmation when the yes flag is passed', async () => { confirmSpy.mockResolvedValue(true); + mockSend.mockResolvedValue({}); await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - deleteDomainName, - }, - }, command: 'appsync domain delete', configExt: { appSync: { @@ -277,8 +312,11 @@ describe('delete domain', () => { }); expect(confirmSpy).not.toHaveBeenCalled(); - expect(deleteDomainName).toHaveBeenCalledTimes(1); - expect(deleteDomainName.mock.calls[0][0]).toMatchInlineSnapshot(` + const deleteCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DeleteDomainNameCommand, + ); + expect(deleteCall).toBeDefined(); + expect(deleteCall![0].input).toMatchInlineSnapshot(` { "domainName": "api.example.com", } @@ -287,14 +325,10 @@ describe('delete domain', () => { it('should not delete a domain, when not confirmed', async () => { confirmSpy.mockResolvedValue(false); + mockSend.mockResolvedValue({}); await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - AppSync: { - deleteDomainName, - }, - }, command: 'appsync domain delete', configExt: { appSync: { @@ -306,38 +340,29 @@ describe('delete domain', () => { }); expect(confirmSpy).toHaveBeenCalled(); - expect(deleteDomainName).not.toHaveBeenCalled(); + const deleteCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DeleteDomainNameCommand, + ); + expect(deleteCall).toBeUndefined(); }); }); describe('assoc domain', () => { - const associateApi = jest.fn(); - const getApiAssociation = jest.fn(); - - afterEach(() => { - associateApi.mockClear(); - getApiAssociation.mockReset(); - }); - it('should associate a domain', async () => { - getApiAssociation + // getApiAssocStatus called twice: first NOT_FOUND, then SUCCESS (polling) + // describeStackResources for getApiId + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand .mockResolvedValueOnce({ - apiAssociation: { - // FIXME: this should throw a ServerlessError instead - associationStatus: 'NOT_FOUND', - }, - }) - .mockResolvedValue({ - apiAssociation: { - associationStatus: 'SUCCESS', - }, - }); + apiAssociation: { associationStatus: 'NOT_FOUND' }, + }) // GetApiAssociationCommand (initial check) + .mockResolvedValueOnce({}) // AssociateApiCommand + .mockResolvedValueOnce({ + apiAssociation: { associationStatus: 'SUCCESS' }, + }); // GetApiAssociationCommand (polling) + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { associateApi, getApiAssociation }, - }, command: 'appsync domain assoc', configExt: { appSync: { @@ -348,24 +373,30 @@ describe('assoc domain', () => { }, }); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(2); - expect(associateApi).toHaveBeenCalledTimes(1); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const associateCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof AssociateApiCommand, + ); + + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(2); + expect(associateCall).toBeDefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, + { + "domainName": "api.example.com", + }, ] `); - expect(associateApi.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(associateCall![0].input).toMatchInlineSnapshot(` { "apiId": "123456789", "domainName": "api.example.com", @@ -374,18 +405,14 @@ describe('assoc domain', () => { }); it('should handle already associated APIs', async () => { - getApiAssociation.mockResolvedValueOnce({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }); + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand + .mockResolvedValueOnce({ + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }); // GetApiAssociationCommand + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { associateApi, getApiAssociation }, - }, command: 'appsync domain assoc', configExt: { appSync: { @@ -396,16 +423,24 @@ describe('assoc domain', () => { }, }); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(1); - expect(associateApi).not.toHaveBeenCalled(); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const associateCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof AssociateApiCommand, + ); + + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(1); + expect(associateCall).toBeUndefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, ] `); }); @@ -413,25 +448,18 @@ describe('assoc domain', () => { it('should ask for confirmation when already associated', async () => { confirmSpy.mockResolvedValue(true); - getApiAssociation + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand .mockResolvedValueOnce({ - apiAssociation: { - apiId: '987654321', - associationStatus: 'SUCCESS', - }, - }) - .mockResolvedValue({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }); + apiAssociation: { apiId: '987654321', associationStatus: 'SUCCESS' }, + }) // GetApiAssociationCommand (initial — different API) + .mockResolvedValueOnce({}) // AssociateApiCommand + .mockResolvedValueOnce({ + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }); // GetApiAssociationCommand (polling) + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { associateApi, getApiAssociation }, - }, command: 'appsync domain assoc', configExt: { appSync: { @@ -442,25 +470,31 @@ describe('assoc domain', () => { }, }); + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const associateCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof AssociateApiCommand, + ); + expect(confirmSpy).toHaveBeenCalled(); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(2); - expect(associateApi).toHaveBeenCalledTimes(1); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(2); + expect(associateCall).toBeDefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, + { + "domainName": "api.example.com", + }, ] `); - expect(associateApi.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(associateCall![0].input).toMatchInlineSnapshot(` { "apiId": "123456789", "domainName": "api.example.com", @@ -470,25 +504,19 @@ describe('assoc domain', () => { it('should not ask for confirmation when yes flag is passed', async () => { confirmSpy.mockResolvedValue(true); - getApiAssociation + + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand .mockResolvedValueOnce({ - apiAssociation: { - apiId: '987654321', - associationStatus: 'SUCCESS', - }, - }) - .mockResolvedValue({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }); + apiAssociation: { apiId: '987654321', associationStatus: 'SUCCESS' }, + }) // GetApiAssociationCommand (initial — different API) + .mockResolvedValueOnce({}) // AssociateApiCommand + .mockResolvedValueOnce({ + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }); // GetApiAssociationCommand (polling) + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { associateApi, getApiAssociation }, - }, command: 'appsync domain assoc', configExt: { appSync: { @@ -502,25 +530,31 @@ describe('assoc domain', () => { }, }); + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const associateCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof AssociateApiCommand, + ); + expect(confirmSpy).not.toHaveBeenCalled(); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(2); - expect(associateApi).toHaveBeenCalledTimes(1); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(2); + expect(associateCall).toBeDefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, + { + "domainName": "api.example.com", + }, ] `); - expect(associateApi.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(associateCall![0].input).toMatchInlineSnapshot(` { "apiId": "123456789", "domainName": "api.example.com", @@ -530,34 +564,21 @@ describe('assoc domain', () => { }); describe('domain disassoc', () => { - const disassociateApi = jest.fn(); - const getApiAssociation = jest.fn(); - - afterEach(() => { - disassociateApi.mockClear(); - getApiAssociation.mockReset(); - }); - it('should disassociate a domain, asking for confirmation ', async () => { confirmSpy.mockResolvedValue(true); - getApiAssociation + + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand .mockResolvedValueOnce({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }) - .mockResolvedValue({ - apiAssociation: { - associationStatus: 'NOT_FOUND', - }, - }); + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }) // GetApiAssociationCommand (initial) + .mockResolvedValueOnce({}) // DisassociateApiCommand + .mockResolvedValueOnce({ + apiAssociation: { associationStatus: 'NOT_FOUND' }, + }); // GetApiAssociationCommand (polling) + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { disassociateApi, getApiAssociation }, - }, command: 'appsync domain disassoc', configExt: { appSync: { @@ -568,25 +589,31 @@ describe('domain disassoc', () => { }, }); + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const disassocCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DisassociateApiCommand, + ); + expect(confirmSpy).toHaveBeenCalled(); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(2); - expect(disassociateApi).toHaveBeenCalledTimes(1); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(2); + expect(disassocCall).toBeDefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, + { + "domainName": "api.example.com", + }, ] `); - expect(disassociateApi.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(disassocCall![0].input).toMatchInlineSnapshot(` { "domainName": "api.example.com", } @@ -595,24 +622,19 @@ describe('domain disassoc', () => { it('should disassociate a domain, skipping confirmation when the yes flag is passed', async () => { confirmSpy.mockResolvedValue(true); - getApiAssociation + + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand .mockResolvedValueOnce({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }) - .mockResolvedValue({ - apiAssociation: { - associationStatus: 'NOT_FOUND', - }, - }); + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }) // GetApiAssociationCommand (initial) + .mockResolvedValueOnce({}) // DisassociateApiCommand + .mockResolvedValueOnce({ + apiAssociation: { associationStatus: 'NOT_FOUND' }, + }); // GetApiAssociationCommand (polling) + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { disassociateApi, getApiAssociation }, - }, command: 'appsync domain disassoc', configExt: { appSync: { @@ -626,25 +648,31 @@ describe('domain disassoc', () => { }, }); + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const disassocCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DisassociateApiCommand, + ); + expect(confirmSpy).not.toHaveBeenCalled(); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(2); - expect(disassociateApi).toHaveBeenCalledTimes(1); - expect(getApiAssociation.mock.calls).toMatchInlineSnapshot(` + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(2); + expect(disassocCall).toBeDefined(); + expect(assocCalls.map(([cmd]) => cmd.input)).toMatchInlineSnapshot(` [ - [ - { - "domainName": "api.example.com", - }, - ], - [ - { - "domainName": "api.example.com", - }, - ], + { + "domainName": "api.example.com", + }, + { + "domainName": "api.example.com", + }, ] `); - expect(disassociateApi.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(disassocCall![0].input).toMatchInlineSnapshot(` { "domainName": "api.example.com", } @@ -653,18 +681,15 @@ describe('domain disassoc', () => { it('should not disassociate a domain, when not confirmed', async () => { confirmSpy.mockResolvedValue(false); - getApiAssociation.mockResolvedValueOnce({ - apiAssociation: { - apiId: '123456789', - associationStatus: 'SUCCESS', - }, - }); + + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand + .mockResolvedValueOnce({ + apiAssociation: { apiId: '123456789', associationStatus: 'SUCCESS' }, + }); // GetApiAssociationCommand + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { disassociateApi, getApiAssociation }, - }, command: 'appsync domain disassoc', configExt: { appSync: { @@ -675,67 +700,54 @@ describe('domain disassoc', () => { }, }); - expect(describeStackResources).toHaveBeenCalledTimes(1); - expect(getApiAssociation).toHaveBeenCalledTimes(1); + const cfCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DescribeStackResourcesCommand, + ); + const assocCalls = mockSend.mock.calls.filter( + ([cmd]) => cmd instanceof GetApiAssociationCommand, + ); + const disassocCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof DisassociateApiCommand, + ); + + expect(cfCall).toBeDefined(); + expect(assocCalls).toHaveLength(1); expect(confirmSpy).toHaveBeenCalled(); - expect(disassociateApi).not.toHaveBeenCalled(); + expect(disassocCall).toBeUndefined(); }); }); describe('domain create-record', () => { - const getDomainName = jest.fn(); - const listHostedZonesByName = jest.fn(); - const changeResourceRecordSets = jest.fn(); - const getChange = jest.fn(); - - beforeEach(() => { - getDomainName.mockResolvedValue({ - domainNameConfig: { - appsyncDomainName: 'qbcdefghij.cloudfront.net', - hostedZoneId: 'Z111111QQQQQQQ', - }, - }); - listHostedZonesByName.mockResolvedValue({ - HostedZones: [ - { - Id: '/hostedzone/KLMNOP', - Name: 'example.com.', - }, - ], - }); - changeResourceRecordSets.mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'PENDING', - }, - }); - getChange.mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'INSYNC', + const getDomainNameResponse = { + domainNameConfig: { + appsyncDomainName: 'qbcdefghij.cloudfront.net', + hostedZoneId: 'Z111111QQQQQQQ', + }, + }; + const listHostedZonesResponse = { + HostedZones: [ + { + Id: '/hostedzone/KLMNOP', + Name: 'example.com.', }, - }); - }); - - afterEach(() => { - getDomainName.mockClear(); - listHostedZonesByName.mockClear(); - changeResourceRecordSets.mockClear(); - getChange.mockClear(); - }); + ], + }; + const changeRecordPendingResponse = { + ChangeInfo: { Id: '1234567890', Status: 'PENDING' }, + }; + const getChangeInsyncResponse = { + ChangeInfo: { Id: '1234567890', Status: 'INSYNC' }, + }; it('should create a route53 record', async () => { + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockResolvedValueOnce(changeRecordPendingResponse) // ChangeResourceRecordSetsCommand + .mockResolvedValueOnce(getChangeInsyncResponse); // GetChangeCommand + await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain create-record', configExt: { appSync: { @@ -746,69 +758,69 @@ describe('domain create-record', () => { }, }); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).toHaveBeenCalledTimes(1); - expect(getDomainName.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "domainName": "api.example.com", - }, - ] + const getDomainCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetDomainNameCommand, + ); + const listZonesCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ); + const changeRecordCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ); + const getChangeCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetChangeCommand, + ); + + expect(getDomainCall).toBeDefined(); + expect(listZonesCall).toBeDefined(); + expect(changeRecordCall).toBeDefined(); + expect(getChangeCall).toBeDefined(); + expect(getDomainCall![0].input).toMatchInlineSnapshot(` + { + "domainName": "api.example.com", + } `); - expect(changeResourceRecordSets.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "ChangeBatch": { - "Changes": [ - { - "Action": "CREATE", - "ResourceRecordSet": { - "AliasTarget": { - "DNSName": "qbcdefghij.cloudfront.net", - "EvaluateTargetHealth": false, - "HostedZoneId": "Z111111QQQQQQQ", - }, - "Name": "api.example.com", - "Type": "A", + expect(changeRecordCall![0].input).toMatchInlineSnapshot(` + { + "ChangeBatch": { + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "AliasTarget": { + "DNSName": "qbcdefghij.cloudfront.net", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z111111QQQQQQQ", }, + "Name": "api.example.com", + "Type": "A", }, - ], - }, - "HostedZoneId": "KLMNOP", + }, + ], }, - ] + "HostedZoneId": "KLMNOP", + } `); - expect(getChange.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "Id": "1234567890", - }, - ] + expect(getChangeCall![0].input).toMatchInlineSnapshot(` + { + "Id": "1234567890", + } `); }); it('should handle changeResourceRecordSets errors', async () => { - changeResourceRecordSets.mockRejectedValue( - new ServerlessError( - "[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]", - ), - ); + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockRejectedValueOnce( + new Error( + "[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]", + ), + ); // ChangeResourceRecordSetsCommand await expect( runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, - command: 'appsync domain create-record', configExt: { appSync: { @@ -822,30 +834,36 @@ describe('domain create-record', () => { `"[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]"`, ); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).not.toHaveBeenCalled(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); }); it('should handle changeResourceRecordSets errors silently', async () => { - changeResourceRecordSets.mockRejectedValue( - new ServerlessError( - "[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]", - ), - ); + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockRejectedValueOnce( + new Error( + "[Tried to create resource record set [name='api.example.com.', type='A'] but it already exists]", + ), + ); // ChangeResourceRecordSetsCommand await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain create-record', configExt: { appSync: { @@ -857,28 +875,32 @@ describe('domain create-record', () => { options: { quiet: true }, }); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).not.toHaveBeenCalled(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); }); it('should handle when appsync domain name not created', async () => { - getDomainName.mockResolvedValue(new ServerlessError('Domain not found')); + mockSend.mockResolvedValueOnce({ + domainNameConfig: undefined, + }); // GetDomainNameCommand — no config await expect( runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, - command: 'appsync domain create-record', configExt: { appSync: { @@ -890,67 +912,60 @@ describe('domain create-record', () => { options: { quiet: true }, }), ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Domain api.example.com not found - Did you forget to run 'sls appsync domain create'?" - `); - - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).not.toHaveBeenCalled(); - expect(changeResourceRecordSets).not.toHaveBeenCalled(); - expect(getChange).not.toHaveBeenCalled(); + "Domain api.example.com not found + Did you forget to run 'sls appsync domain create'?" + `); + + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeUndefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeUndefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); }); }); describe('domain delete-record', () => { - const getDomainName = jest.fn().mockResolvedValue({ + const getDomainNameResponse = { domainNameConfig: { appsyncDomainName: 'qbcdefghij.cloudfront.net', hostedZoneId: 'Z111111QQQQQQQ', }, - }); - const listHostedZonesByName = jest.fn().mockResolvedValue({ + }; + const listHostedZonesResponse = { HostedZones: [ { Id: '/hostedzone/KLMNOP', Name: 'example.com.', }, ], - }); - const changeResourceRecordSets = jest.fn(); - const getChange = jest.fn().mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'INSYNC', - }, - }); - - afterEach(() => { - getDomainName.mockClear(); - listHostedZonesByName.mockClear(); - changeResourceRecordSets.mockClear(); - getChange.mockClear(); - }); + }; it('should delete a route53 record, asking for confirmation', async () => { confirmSpy.mockResolvedValue(true); - changeResourceRecordSets.mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'PENDING', - }, - }); + + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockResolvedValueOnce({ + ChangeInfo: { Id: '1234567890', Status: 'PENDING' }, + }) // ChangeResourceRecordSetsCommand + .mockResolvedValueOnce({ + ChangeInfo: { Id: '1234567890', Status: 'INSYNC' }, + }); // GetChangeCommand await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain delete-record', configExt: { appSync: { @@ -961,70 +976,66 @@ describe('domain delete-record', () => { }, }); + const getDomainCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetDomainNameCommand, + ); + const listZonesCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ); + const changeRecordCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ); + const getChangeCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetChangeCommand, + ); + expect(confirmSpy).toHaveBeenCalled(); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).toHaveBeenCalledTimes(1); - expect(getDomainName.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "domainName": "api.example.com", - }, - ] + expect(getDomainCall).toBeDefined(); + expect(listZonesCall).toBeDefined(); + expect(changeRecordCall).toBeDefined(); + expect(getChangeCall).toBeDefined(); + expect(getDomainCall![0].input).toMatchInlineSnapshot(` + { + "domainName": "api.example.com", + } `); - expect(changeResourceRecordSets.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "ChangeBatch": { - "Changes": [ - { - "Action": "DELETE", - "ResourceRecordSet": { - "AliasTarget": { - "DNSName": "qbcdefghij.cloudfront.net", - "EvaluateTargetHealth": false, - "HostedZoneId": "Z111111QQQQQQQ", - }, - "Name": "api.example.com", - "Type": "A", + expect(changeRecordCall![0].input).toMatchInlineSnapshot(` + { + "ChangeBatch": { + "Changes": [ + { + "Action": "DELETE", + "ResourceRecordSet": { + "AliasTarget": { + "DNSName": "qbcdefghij.cloudfront.net", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z111111QQQQQQQ", }, + "Name": "api.example.com", + "Type": "A", }, - ], - }, - "HostedZoneId": "KLMNOP", + }, + ], }, - ] + "HostedZoneId": "KLMNOP", + } `); - expect(getChange.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "Id": "1234567890", - }, - ] + expect(getChangeCall![0].input).toMatchInlineSnapshot(` + { + "Id": "1234567890", + } `); }); it('should not delete a route53 record, when not confirmed', async () => { confirmSpy.mockResolvedValue(false); - changeResourceRecordSets.mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'PENDING', - }, - }); + + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse); // ListHostedZonesByNameCommand await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain delete-record', configExt: { appSync: { @@ -1036,11 +1047,27 @@ describe('domain delete-record', () => { }); expect(confirmSpy).toHaveBeenCalled(); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).not.toHaveBeenCalled(); - expect(getChange).not.toHaveBeenCalled(); - expect(getDomainName.mock.calls[0]).toMatchInlineSnapshot(` + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeUndefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); + expect( + mockSend.mock.calls + .filter(([cmd]) => cmd instanceof GetDomainNameCommand) + .map(([cmd]) => cmd.input), + ).toMatchInlineSnapshot(` [ { "domainName": "api.example.com", @@ -1051,24 +1078,19 @@ describe('domain delete-record', () => { it('should delete a route53 record, skipping confirmation when the yes flag is passed', async () => { confirmSpy.mockResolvedValue(true); - changeResourceRecordSets.mockResolvedValue({ - ChangeInfo: { - Id: '1234567890', - Status: 'PENDING', - }, - }); + + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockResolvedValueOnce({ + ChangeInfo: { Id: '1234567890', Status: 'PENDING' }, + }) // ChangeResourceRecordSetsCommand + .mockResolvedValueOnce({ + ChangeInfo: { Id: '1234567890', Status: 'INSYNC' }, + }); // GetChangeCommand await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain delete-record', configExt: { appSync: { @@ -1080,71 +1102,73 @@ describe('domain delete-record', () => { options: { yes: true }, }); + const getDomainCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetDomainNameCommand, + ); + const changeRecordCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ); + const getChangeCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetChangeCommand, + ); + expect(confirmSpy).not.toHaveBeenCalled(); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).toHaveBeenCalledTimes(1); - expect(getDomainName.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "domainName": "api.example.com", - }, - ] + expect(getDomainCall).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect(changeRecordCall).toBeDefined(); + expect(getChangeCall).toBeDefined(); + expect(getDomainCall![0].input).toMatchInlineSnapshot(` + { + "domainName": "api.example.com", + } `); - expect(changeResourceRecordSets.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "ChangeBatch": { - "Changes": [ - { - "Action": "DELETE", - "ResourceRecordSet": { - "AliasTarget": { - "DNSName": "qbcdefghij.cloudfront.net", - "EvaluateTargetHealth": false, - "HostedZoneId": "Z111111QQQQQQQ", - }, - "Name": "api.example.com", - "Type": "A", + expect(changeRecordCall![0].input).toMatchInlineSnapshot(` + { + "ChangeBatch": { + "Changes": [ + { + "Action": "DELETE", + "ResourceRecordSet": { + "AliasTarget": { + "DNSName": "qbcdefghij.cloudfront.net", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z111111QQQQQQQ", }, + "Name": "api.example.com", + "Type": "A", }, - ], - }, - "HostedZoneId": "KLMNOP", + }, + ], }, - ] + "HostedZoneId": "KLMNOP", + } `); - expect(getChange.mock.calls[0]).toMatchInlineSnapshot(` - [ - { - "Id": "1234567890", - }, - ] + expect(getChangeCall![0].input).toMatchInlineSnapshot(` + { + "Id": "1234567890", + } `); }); it('should handle changeResourceRecordSets errors', async () => { confirmSpy.mockResolvedValue(true); - changeResourceRecordSets.mockRejectedValue( - new ServerlessError( - "[Tried to delete resource record set [name='api.example.com.', type='A'] but it was not found]", - ), - ); + + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockRejectedValueOnce( + new Error( + "[Tried to delete resource record set [name='api.example.com.', type='A'] but it was not found]", + ), + ); // ChangeResourceRecordSetsCommand await expect( runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, - command: 'appsync domain delete-record', configExt: { appSync: { @@ -1159,31 +1183,38 @@ describe('domain delete-record', () => { ); expect(confirmSpy).toHaveBeenCalled(); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).not.toHaveBeenCalled(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); }); it('should handle changeResourceRecordSets errors silently', async () => { confirmSpy.mockResolvedValue(true); - changeResourceRecordSets.mockRejectedValue( - new ServerlessError( - "[Tried to delete resource record set [name='api.example.com.', type='A'] but it was not found]", - ), - ); + + mockSend + .mockResolvedValueOnce(getDomainNameResponse) // GetDomainNameCommand + .mockResolvedValueOnce(listHostedZonesResponse) // ListHostedZonesByNameCommand + .mockRejectedValueOnce( + new Error( + "[Tried to delete resource record set [name='api.example.com.', type='A'] but it was not found]", + ), + ); // ChangeResourceRecordSetsCommand await runServerless({ fixture: 'appsync', - awsRequestStubMap: { - CloudFormation: { describeStackResources }, - AppSync: { getDomainName }, - Route53: { - changeResourceRecordSets, - listHostedZonesByName, - getChange, - }, - }, command: 'appsync domain delete-record', configExt: { appSync: { @@ -1198,9 +1229,321 @@ describe('domain delete-record', () => { }); expect(confirmSpy).toHaveBeenCalled(); - expect(getDomainName).toHaveBeenCalledTimes(1); - expect(listHostedZonesByName).toHaveBeenCalledTimes(1); - expect(changeResourceRecordSets).toHaveBeenCalledTimes(1); - expect(getChange).not.toHaveBeenCalled(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetDomainNameCommand), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ListHostedZonesByNameCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find( + ([cmd]) => cmd instanceof ChangeResourceRecordSetsCommand, + ), + ).toBeDefined(); + expect( + mockSend.mock.calls.find(([cmd]) => cmd instanceof GetChangeCommand), + ).toBeUndefined(); + }); +}); + +describe('getApiAssocStatus error handling', () => { + it('returns { associationStatus: "NOT_FOUND" } when SDK throws NotFoundException', async () => { + const notFoundError = Object.assign(new Error('Domain not found'), { + name: 'NotFoundException', + }); + mockSend.mockRejectedValueOnce(notFoundError); + + const instance = plugin(); + const result = await instance.getApiAssocStatus('api.example.com'); + + expect(result).toEqual({ associationStatus: 'NOT_FOUND' }); + }); + + it('re-throws unknown errors unchanged', async () => { + const unknownError = Object.assign(new Error('Internal server error'), { + name: 'InternalFailureException', + }); + mockSend.mockRejectedValueOnce(unknownError); + + const instance = plugin(); + await expect(instance.getApiAssocStatus('api.example.com')).rejects.toThrow( + unknownError, + ); + }); + + it('re-throws the exact same error object for unknown errors', async () => { + const unknownError = Object.assign(new Error('Service unavailable'), { + name: 'ServiceUnavailableException', + }); + mockSend.mockRejectedValueOnce(unknownError); + + const instance = plugin(); + let thrownError: unknown; + try { + await instance.getApiAssocStatus('api.example.com'); + } catch (e) { + thrownError = e; + } + + expect(thrownError).toBe(unknownError); + }); + + it('does not swallow NotFoundException when name is a different error code', async () => { + const badRequestError = Object.assign(new Error('Bad request'), { + name: 'BadRequestException', + }); + mockSend.mockRejectedValueOnce(badRequestError); + + const instance = plugin(); + await expect(instance.getApiAssocStatus('api.example.com')).rejects.toThrow( + 'Bad request', + ); + }); +}); + +describe('evaluate resolver (JS)', () => { + it('should evaluate a JS resolver request function and print the result', async () => { + mockSend.mockResolvedValueOnce({ + evaluationResult: '{"operation":"GetItem"}', + logs: [], + }); // EvaluateCodeCommand + + const instance = plugin(); + // Inject a UNIT JS resolver into the config + (instance as any).api = { + config: { + resolvers: { + 'Query.getUser': { + kind: 'UNIT', + code: __filename, // use this test file as a stand-in (exists on disk) + }, + }, + }, + }; + (instance as any).naming = {}; + (instance as any).options = { + type: 'Query', + field: 'getUser', + function: 'request', + context: '{}', + }; + + await instance.evaluateResolver(); + + const evalCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof EvaluateCodeCommand, + ); + expect(evalCall).toBeDefined(); + expect(evalCall![0].input.function).toBe('request'); + expect((instance as any).utils.writeText).toHaveBeenCalledWith( + '{"operation":"GetItem"}', + ); + }); + + it('should log errors when JS evaluation fails', async () => { + mockSend.mockResolvedValueOnce({ + error: { + message: 'Runtime error', + codeErrors: [ + { + value: 'undefined is not a function', + location: { line: 5, column: 3 }, + }, + ], + }, + logs: ['log line 1'], + }); // EvaluateCodeCommand + + const instance = plugin(); + (instance as any).api = { + config: { + resolvers: { + 'Query.getUser': { + kind: 'UNIT', + code: __filename, + }, + }, + }, + }; + (instance as any).naming = {}; + (instance as any).options = { + type: 'Query', + field: 'getUser', + function: 'request', + context: '{}', + }; + + await instance.evaluateResolver(); + + expect((instance as any).utils.log.error).toHaveBeenCalledWith( + expect.stringContaining('Runtime error'), + ); + }); + + it('should throw when resolver is not found', async () => { + const instance = plugin(); + (instance as any).api = { + config: { resolvers: {} }, + }; + (instance as any).naming = {}; + (instance as any).options = { + type: 'Query', + field: 'missing', + context: '{}', + }; + + await expect(instance.evaluateResolver()).rejects.toThrow( + "Resolver 'Query.missing' not found in configuration.", + ); + }); + + it('should throw when neither --template nor --type/--field are provided', async () => { + const instance = plugin(); + (instance as any).api = { config: { resolvers: {} } }; + (instance as any).naming = {}; + (instance as any).options = { context: '{}' }; + + await expect(instance.evaluateResolver()).rejects.toThrow( + 'You must specify either --template (VTL) or both --type and --field (JS resolver).', + ); + }); +}); + +describe('evaluate resolver (VTL template)', () => { + it('should evaluate a VTL template and print the result', async () => { + mockSend.mockResolvedValueOnce({ + evaluationResult: '{"version":"2018-05-29"}', + }); // EvaluateMappingTemplateCommand + + const instance = plugin(); + (instance as any).options = { + template: __filename, // exists on disk + context: '{}', + }; + + await instance.evaluateResolver(); + + const evalCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof EvaluateMappingTemplateCommand, + ); + expect(evalCall).toBeDefined(); + expect((instance as any).utils.writeText).toHaveBeenCalledWith( + '{"version":"2018-05-29"}', + ); + }); + + it('should log error when VTL evaluation fails', async () => { + mockSend.mockResolvedValueOnce({ + error: { message: 'Template syntax error' }, + }); // EvaluateMappingTemplateCommand + + const instance = plugin(); + (instance as any).options = { + template: __filename, + context: '{}', + }; + + await instance.evaluateResolver(); + + expect((instance as any).utils.log.error).toHaveBeenCalledWith( + expect.stringContaining('Template syntax error'), + ); + }); + + it('should throw when template file does not exist', async () => { + const instance = plugin(); + (instance as any).options = { + template: '/nonexistent/path/template.vtl', + context: '{}', + }; + + await expect(instance.evaluateResolver()).rejects.toThrow( + 'Template file not found', + ); + }); +}); + +describe('env get', () => { + it('should print environment variables', async () => { + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand + .mockResolvedValueOnce({ + environmentVariables: { TABLE_NAME: 'prod-table', STAGE: 'prod' }, + }); // GetGraphqlApiEnvironmentVariablesCommand + + await runServerless({ + fixture: 'appsync', + command: 'appsync env get', + }); + + const envCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof GetGraphqlApiEnvironmentVariablesCommand, + ); + expect(envCall).toBeDefined(); + expect(envCall![0].input).toMatchInlineSnapshot(` + { + "apiId": "123456789", + } + `); + }); + + it('should log info when no environment variables are set', async () => { + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand + .mockResolvedValueOnce({ environmentVariables: {} }); // GetGraphqlApiEnvironmentVariablesCommand + + const instance = plugin(); + (instance as any).options = {}; + + // Manually wire getApiId to return a fixed ID + jest.spyOn(instance, 'getApiId').mockResolvedValue('123456789'); + + await instance.envGet(); + + expect((instance as any).utils.log.info).toHaveBeenCalledWith( + 'No environment variables set for this API.', + ); + }); +}); + +describe('env set', () => { + it('should set an environment variable (merging with existing)', async () => { + mockSend + .mockResolvedValueOnce(describeStackResourcesResponse) // DescribeStackResourcesCommand + .mockResolvedValueOnce({ + environmentVariables: { EXISTING_KEY: 'existing-value' }, + }) // GetGraphqlApiEnvironmentVariablesCommand + .mockResolvedValueOnce({}); // PutGraphqlApiEnvironmentVariablesCommand + + await runServerless({ + fixture: 'appsync', + command: 'appsync env set', + options: { key: 'NEW_KEY', value: 'new-value' }, + }); + + const putCall = mockSend.mock.calls.find( + ([cmd]) => cmd instanceof PutGraphqlApiEnvironmentVariablesCommand, + ); + expect(putCall).toBeDefined(); + expect(putCall![0].input).toMatchInlineSnapshot(` + { + "apiId": "123456789", + "environmentVariables": { + "EXISTING_KEY": "existing-value", + "NEW_KEY": "new-value", + }, + } + `); + }); + + it('should throw when --key or --value is missing', async () => { + const instance = plugin(); + (instance as any).options = { key: 'ONLY_KEY' }; + + await expect(instance.envSet()).rejects.toThrow( + 'You must specify both --key and --value.', + ); }); }); diff --git a/src/__tests__/error-handling.pbt.test.ts b/src/__tests__/error-handling.pbt.test.ts new file mode 100644 index 00000000..43e04889 --- /dev/null +++ b/src/__tests__/error-handling.pbt.test.ts @@ -0,0 +1,107 @@ +/** + * Property-based tests for error handling in ServerlessAppsyncPlugin (Properties 3 & 4). + * Tests getApiAssocStatus() with a mocked AwsClientFactory. + * + * Feature: aws-sdk-v3-migration + */ + +import * as fc from 'fast-check'; +import { plugin } from './given'; + +// Mock AwsClientFactory so no real AWS credentials or SDK calls are made. +const mockSend = jest.fn(); +jest.mock('../aws-client-factory', () => ({ + AwsClientFactory: jest.fn().mockImplementation(() => ({ + getAppSyncClient: () => ({ send: mockSend }), + getCloudFormationClient: () => ({ send: mockSend }), + getCloudWatchLogsClient: () => ({ send: mockSend }), + getRoute53Client: () => ({ send: mockSend }), + getAcmClient: () => ({ send: mockSend }), + })), +})); + +// Avoid ESM dynamic import issues with credential-providers in Jest +jest.mock('@aws-sdk/credential-providers', () => ({ + fromNodeProviderChain: jest.fn().mockReturnValue({}), +})); + +beforeEach(() => { + mockSend.mockReset(); +}); + +// --------------------------------------------------------------------------- +// Property 3: NotFoundException is mapped to NOT_FOUND association status +// Feature: aws-sdk-v3-migration, Property 3: NotFoundException is mapped to NOT_FOUND association status +// --------------------------------------------------------------------------- +describe('Property 3: NotFoundException is mapped to NOT_FOUND association status', () => { + it('getApiAssocStatus returns { associationStatus: NOT_FOUND } for any domain name when NotFoundException is thrown', async () => { + await fc.assert( + fc.asyncProperty(fc.string({ minLength: 1 }), async (domainName) => { + const notFoundError = Object.assign(new Error('Domain not found'), { + name: 'NotFoundException', + }); + mockSend.mockRejectedValue(notFoundError); + + const p = plugin(); + const result = await p.getApiAssocStatus(domainName); + + expect(result).toEqual({ associationStatus: 'NOT_FOUND' }); + }), + { numRuns: 100 }, + ); + }); +}); + +// --------------------------------------------------------------------------- +// Property 4: Unexpected errors are re-thrown unchanged +// Feature: aws-sdk-v3-migration, Property 4: Unexpected errors are re-thrown unchanged +// --------------------------------------------------------------------------- +describe('Property 4: Unexpected errors are re-thrown unchanged', () => { + it('getApiAssocStatus re-throws the original error when error.name is not NotFoundException', async () => { + await fc.assert( + fc.asyncProperty( + fc.string({ minLength: 1 }).filter((s) => s !== 'NotFoundException'), + async (errorName) => { + const unexpectedError = Object.assign( + new Error('Unexpected failure'), + { + name: errorName, + }, + ); + mockSend.mockRejectedValue(unexpectedError); + + const p = plugin(); + await expect(p.getApiAssocStatus('some-domain')).rejects.toThrow( + unexpectedError, + ); + }, + ), + { numRuns: 100 }, + ); + }); + + it('re-thrown error is the exact same object reference (not a wrapped copy)', async () => { + await fc.assert( + fc.asyncProperty( + fc.string({ minLength: 1 }).filter((s) => s !== 'NotFoundException'), + async (errorName) => { + const originalError = Object.assign(new Error('Original error'), { + name: errorName, + }); + mockSend.mockRejectedValue(originalError); + + const p = plugin(); + let caughtError: unknown; + try { + await p.getApiAssocStatus('some-domain'); + } catch (e) { + caughtError = e; + } + + expect(caughtError).toBe(originalError); + }, + ), + { numRuns: 100 }, + ); + }); +}); diff --git a/src/aws-client-factory.ts b/src/aws-client-factory.ts new file mode 100644 index 00000000..026ba75a --- /dev/null +++ b/src/aws-client-factory.ts @@ -0,0 +1,92 @@ +import { AppSyncClient } from '@aws-sdk/client-appsync'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'; +import { Route53Client } from '@aws-sdk/client-route-53'; +import { ACMClient } from '@aws-sdk/client-acm'; +import type { AwsCredentialIdentityProvider } from '@aws-sdk/types'; + +// ACM certificates for CloudFront must always be in us-east-1 +const ACM_REGION = 'us-east-1'; + +/** + * AWS credentials accepted by SDK v3 clients. + * Either a static credential object or an AwsCredentialIdentityProvider function. + */ +export type AwsCredentials = + | { + accessKeyId: string; + secretAccessKey: string; + sessionToken?: string; + } + | AwsCredentialIdentityProvider; + +/** + * Centralized factory for creating and caching AWS SDK v3 clients. + * Clients are lazily initialized on first access and reused across calls. + */ +export class AwsClientFactory { + private appSyncClient?: AppSyncClient; + private cloudFormationClient?: CloudFormationClient; + private cloudWatchLogsClient?: CloudWatchLogsClient; + private route53Client?: Route53Client; + private acmClient?: ACMClient; + + constructor( + private readonly region: string, + private readonly credentials: AwsCredentials, + ) {} + + getAppSyncClient(): AppSyncClient { + if (!this.appSyncClient) { + this.appSyncClient = new AppSyncClient({ + region: this.region, + credentials: this.credentials, + }); + } + return this.appSyncClient; + } + + getCloudFormationClient(): CloudFormationClient { + if (!this.cloudFormationClient) { + this.cloudFormationClient = new CloudFormationClient({ + region: this.region, + credentials: this.credentials, + }); + } + return this.cloudFormationClient; + } + + getCloudWatchLogsClient(): CloudWatchLogsClient { + if (!this.cloudWatchLogsClient) { + this.cloudWatchLogsClient = new CloudWatchLogsClient({ + region: this.region, + credentials: this.credentials, + }); + } + return this.cloudWatchLogsClient; + } + + getRoute53Client(): Route53Client { + if (!this.route53Client) { + this.route53Client = new Route53Client({ + region: this.region, + credentials: this.credentials, + }); + } + return this.route53Client; + } + + /** + * ACM client always uses us-east-1, regardless of the configured region. + * This is required because ACM certificates for CloudFront must be in us-east-1. + */ + getAcmClient(): ACMClient { + if (!this.acmClient) { + this.acmClient = new ACMClient({ + region: ACM_REGION, + credentials: this.credentials, + }); + } + return this.acmClient; + } +} diff --git a/src/get-stack-value.ts b/src/get-stack-value.ts deleted file mode 100644 index 2ac554a9..00000000 --- a/src/get-stack-value.ts +++ /dev/null @@ -1,28 +0,0 @@ -function getServerlessStackName(provider) { - return provider.naming.getStackName(); -} - -function getValue(provider, value, name) { - if (typeof value === 'string') { - return Promise.resolve(value); - } else if (value && typeof value.Ref === 'string') { - return provider - .request('CloudFormation', 'listStackResources', { - StackName: getServerlessStackName(provider), - }) - .then((result) => { - const resource = result.StackResourceSummaries.find( - (r) => r.LogicalResourceId === value.Ref, - ); - if (!resource) { - throw new Error(`${name}: Ref "${value.Ref} not found`); - } - - return resource.PhysicalResourceId; - }); - } - - return Promise.reject(new Error(`${value} is not a valid ${name}`)); -} - -export { getServerlessStackName, getValue }; diff --git a/src/index.ts b/src/index.ts index da62e3e0..f315d889 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,39 +8,12 @@ import chalk from 'chalk'; import path from 'path'; import open from 'open'; import fs from 'fs'; -import { - DescribeStackResourcesInput, - DescribeStackResourcesOutput, -} from 'aws-sdk/clients/cloudformation'; -import { - AssociateApiRequest, - AssociateApiResponse, - CreateDomainNameRequest, - DeleteDomainNameRequest, - DeleteDomainNameResponse, - DisassociateApiRequest, - DisassociateApiResponse, - GetApiAssociationRequest, - GetApiAssociationResponse, - GetDomainNameRequest, - GetDomainNameResponse, - GetGraphqlApiRequest, - GetGraphqlApiResponse, - GetIntrospectionSchemaRequest, - GetIntrospectionSchemaResponse, - ListApiKeysRequest, - ListApiKeysResponse, -} from 'aws-sdk/clients/appsync'; import { CommandsDefinition, Hook, VariablesSourcesDefinition, VariableSourceResolver, } from 'serverless'; -import { - FilterLogEventsResponse, - FilterLogEventsRequest, -} from 'aws-sdk/clients/cloudwatchlogs'; import { AppSyncValidationError, validateConfig } from './validation'; import { confirmAction, @@ -51,22 +24,83 @@ import { } from './utils'; import { Api } from './resources/Api'; import { Naming } from './resources/Naming'; +import { buildSync } from 'esbuild'; +import terminalLink from 'terminal-link'; +import { AwsClientFactory, AwsCredentials } from './aws-client-factory'; +import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { - ChangeResourceRecordSetsRequest, - ChangeResourceRecordSetsResponse, - GetChangeRequest, - GetChangeResponse, - ListHostedZonesByNameRequest, - ListHostedZonesByNameResponse, -} from 'aws-sdk/clients/route53'; + GetGraphqlApiCommand, + ListApiKeysCommand, + GetIntrospectionSchemaCommand, + FlushApiCacheCommand, + CreateDomainNameCommand, + DeleteDomainNameCommand, + GetApiAssociationCommand, + AssociateApiCommand, + DisassociateApiCommand, + GetDomainNameCommand, + EvaluateCodeCommand, + EvaluateMappingTemplateCommand, + GetGraphqlApiEnvironmentVariablesCommand, + PutGraphqlApiEnvironmentVariablesCommand, + RuntimeName, +} from '@aws-sdk/client-appsync'; +import { DescribeStackResourcesCommand } from '@aws-sdk/client-cloudformation'; +import { FilterLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs'; import { - ListCertificatesRequest, - ListCertificatesResponse, -} from 'aws-sdk/clients/acm'; -import terminalLink from 'terminal-link'; + ListHostedZonesByNameCommand, + ChangeResourceRecordSetsCommand, + GetChangeCommand, +} from '@aws-sdk/client-route-53'; +import { ListCertificatesCommand } from '@aws-sdk/client-acm'; const CONSOLE_BASE_URL = 'https://console.aws.amazon.com'; +/** + * Build an AWS SDK v3 credential provider from the credentials the Serverless + * Framework resolves for this service (default profile, provider.profile, + * environment credentials, --aws-profile, etc.), so the live commands use the + * same identity the Framework uses for deploys instead of the bare default + * chain. Resolution is fully lazy: the Framework's `getCredentials()` is only + * consulted when a client actually makes a request, so credential-free commands + * (eg: package / offline synthesis) never trigger it. The Framework returns an + * aws-sdk (v2) credentials object which may itself resolve lazily (shared-ini, + * SSO, assume-role, MFA), so we await it. If the Framework resolves no explicit + * credentials (or resolution fails), we defer to the standard v3 default chain. + */ +const resolveCredentials = (provider: Provider): AwsCredentials => { + return async () => { + let credentials; + try { + ({ credentials } = provider.getCredentials()); + } catch { + // Credential resolution can throw in minimal/standalone contexts; fall + // back to the default chain rather than crashing the command. + credentials = undefined; + } + if (!credentials) { + return fromNodeProviderChain()(); + } + if (typeof credentials.getPromise === 'function') { + await credentials.getPromise(); + } + if (!credentials.accessKeyId || !credentials.secretAccessKey) { + // Serverless returned a credentials object but it could not be resolved + // to concrete keys; defer to the default chain rather than handing the + // SDK an empty identity. + return fromNodeProviderChain()(); + } + return { + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken, + expiration: credentials.expireTime + ? new Date(credentials.expireTime) + : undefined, + }; + }; +}; + type Progress = { remove: () => void; }; @@ -86,6 +120,7 @@ type ServerlessPluginUtils = { class ServerlessAppsyncPlugin { private provider: Provider; + private clientFactory: AwsClientFactory; private gatheredData: { apis: { id: string; @@ -116,6 +151,17 @@ class ServerlessAppsyncPlugin { this.options = options; this.provider = this.serverless.getProvider('aws'); this.utils = utils; + + // Resolve region and credentials the same way the Serverless Framework + // does for its own AWS calls, so the live commands honor the --region / + // --aws-profile CLI options, provider.region / provider.profile from the + // service config, and any assumed-role / environment credentials. Falling + // back to the bare default credential chain (as a plain + // `fromNodeProviderChain()` would) silently ignores all of the above and + // breaks profile-based and multi-account setups. + const region = this.provider.getRegion(); + const credentials = resolveCredentials(this.provider); + this.clientFactory = new AwsClientFactory(region, credentials); // We are using a newer version of AJV than Serverless Framework // and some customizations (eg: custom errors, $merge, filter irrelevant errors) // For SF, just validate the type of input to allow us to use a custom @@ -199,6 +245,69 @@ class ServerlessAppsyncPlugin { }, }, }, + evaluate: { + usage: 'Evaluate a resolver or mapping template against a context', + lifecycleEvents: ['run'], + options: { + type: { + usage: 'GraphQL type (e.g. Query)', + shortcut: 't', + required: false, + type: 'string', + }, + field: { + usage: 'GraphQL field (e.g. getUser)', + shortcut: 'f', + required: false, + type: 'string', + }, + function: { + usage: 'Function to evaluate: request or response', + required: false, + type: 'string', + }, + template: { + usage: 'Path to a VTL mapping template file', + required: false, + type: 'string', + }, + context: { + usage: + 'Path to a JSON file with the evaluation context, or inline JSON string', + shortcut: 'c', + required: false, + type: 'string', + }, + }, + }, + env: { + usage: 'Manage AppSync API environment variables', + commands: { + get: { + usage: 'Get all environment variables of the deployed API', + lifecycleEvents: ['run'], + }, + set: { + usage: + 'Set (replace all) environment variables of the deployed API', + lifecycleEvents: ['run'], + options: { + key: { + usage: 'Environment variable key', + shortcut: 'k', + required: true, + type: 'string', + }, + value: { + usage: 'Environment variable value', + shortcut: 'v', + required: true, + type: 'string', + }, + }, + }, + }, + }, domain: { usage: 'Manage the domain for this AppSync API', commands: { @@ -328,6 +437,9 @@ class ServerlessAppsyncPlugin { 'appsync:console:run': () => this.openConsole(), 'appsync:cloudwatch:run': () => this.openCloudWatch(), 'appsync:logs:run': async () => this.initShowLogs(), + 'appsync:evaluate:run': async () => this.evaluateResolver(), + 'appsync:env:get:run': async () => this.envGet(), + 'appsync:env:set:run': async () => this.envSet(), 'before:appsync:domain:create:run': async () => this.initDomainCommand(), 'appsync:domain:create:run': async () => this.createDomain(), 'before:appsync:domain:delete:run': async () => this.initDomainCommand(), @@ -377,15 +489,18 @@ class ServerlessAppsyncPlugin { const logicalIdGraphQLApi = this.naming.getApiLogicalId(); - const { StackResources } = await this.provider.request< - DescribeStackResourcesInput, - DescribeStackResourcesOutput - >('CloudFormation', 'describeStackResources', { - StackName: this.provider.naming.getStackName(), - LogicalResourceId: logicalIdGraphQLApi, - }); + const { StackResources } = await this.clientFactory + .getCloudFormationClient() + .send( + new DescribeStackResourcesCommand({ + StackName: this.provider.naming.getStackName(), + LogicalResourceId: logicalIdGraphQLApi, + }), + ); - const apiId = last(StackResources?.[0]?.PhysicalResourceId?.split('/')); + const apiId = last(StackResources?.[0]?.PhysicalResourceId?.split('/')) as + | string + | undefined; if (!apiId) { throw new this.serverless.classes.Error( @@ -399,12 +514,9 @@ class ServerlessAppsyncPlugin { async gatherData() { const apiId = await this.getApiId(); - const { graphqlApi } = await this.provider.request< - GetGraphqlApiRequest, - GetGraphqlApiResponse - >('AppSync', 'getGraphqlApi', { - apiId, - }); + const { graphqlApi } = await this.clientFactory + .getAppSyncClient() + .send(new GetGraphqlApiCommand({ apiId })); forEach(graphqlApi?.uris, (value, type) => { this.gatheredData.apis.push({ @@ -414,12 +526,9 @@ class ServerlessAppsyncPlugin { }); }); - const { apiKeys } = await this.provider.request< - ListApiKeysRequest, - ListApiKeysResponse - >('AppSync', 'listApiKeys', { - apiId: apiId, - }); + const { apiKeys } = await this.clientFactory + .getAppSyncClient() + .send(new ListApiKeysCommand({ apiId })); apiKeys?.forEach((apiKey) => { this.gatheredData.apiKeys.push({ @@ -432,13 +541,12 @@ class ServerlessAppsyncPlugin { async getIntrospection() { const apiId = await this.getApiId(); - const { schema } = await this.provider.request< - GetIntrospectionSchemaRequest, - GetIntrospectionSchemaResponse - >('AppSync', 'getIntrospectionSchema', { - apiId, - format: (this.options.format || 'JSON').toUpperCase(), - }); + const { schema } = await this.clientFactory.getAppSyncClient().send( + new GetIntrospectionSchemaCommand({ + apiId, + format: (this.options.format || 'JSON').toUpperCase() as 'JSON' | 'SDL', + }), + ); if (!schema) { throw new this.serverless.classes.Error('Schema not found'); @@ -447,7 +555,7 @@ class ServerlessAppsyncPlugin { if (this.options.output) { try { const filePath = path.resolve(this.options.output); - fs.writeFileSync(filePath, schema.toString()); + fs.writeFileSync(filePath, Buffer.from(schema).toString()); this.utils.log.success(`Introspection schema exported to ${filePath}`); } catch (error) { this.utils.log.error( @@ -457,12 +565,14 @@ class ServerlessAppsyncPlugin { return; } - this.utils.writeText(schema.toString()); + this.utils.writeText(Buffer.from(schema).toString()); } async flushCache() { const apiId = await this.getApiId(); - await this.provider.request('AppSync', 'flushApiCache', { apiId }); + await this.clientFactory + .getAppSyncClient() + .send(new FlushApiCacheCommand({ apiId })); this.utils.log.success('Cache flushed successfully'); } @@ -493,15 +603,16 @@ class ServerlessAppsyncPlugin { startTime = DateTime.now().minus({ minutes: 10 }); } - const { events, nextToken: newNextToken } = await this.provider.request< - FilterLogEventsRequest, - FilterLogEventsResponse - >('CloudWatchLogs', 'filterLogEvents', { - logGroupName, - startTime: startTime.toMillis(), - nextToken, - filterPattern: this.options.filter, - }); + const { events, nextToken: newNextToken } = await this.clientFactory + .getCloudWatchLogsClient() + .send( + new FilterLogEventsCommand({ + logGroupName, + startTime: startTime.toMillis(), + nextToken, + filterPattern: this.options.filter, + }), + ); events?.forEach((event) => { const { timestamp, message } = event; @@ -512,7 +623,7 @@ class ServerlessAppsyncPlugin { ); }); - const lastTs = last(events)?.timestamp; + const lastTs = (last(events) as any)?.timestamp; this.options.startTime = lastTs ? DateTime.fromMillis(lastTs + 1).toISO() : this.options.startTime; @@ -565,17 +676,14 @@ class ServerlessAppsyncPlugin { } async getDomainCertificateArn() { - const { CertificateSummaryList } = await this.provider.request< - ListCertificatesRequest, - ListCertificatesResponse - >( - 'ACM', - 'listCertificates', - // only fully issued certificates - { CertificateStatuses: ['ISSUED'] }, - // certificates must always be in us-east-1 - { region: 'us-east-1' }, - ); + const { CertificateSummaryList } = await this.clientFactory + .getAcmClient() + .send( + new ListCertificatesCommand({ + // only fully issued certificates + CertificateStatuses: ['ISSUED'], + }), + ); const domain = this.getDomain(); @@ -607,19 +715,15 @@ class ServerlessAppsyncPlugin { ); } - await this.provider.request< - CreateDomainNameRequest, - CreateDomainNameRequest - >('AppSync', 'createDomainName', { - domainName: domain.name, - certificateArn, - }); + await this.clientFactory.getAppSyncClient().send( + new CreateDomainNameCommand({ + domainName: domain.name, + certificateArn, + }), + ); this.utils.log.success(`Domain '${domain.name}' created successfully`); } catch (error) { - if ( - error instanceof this.serverless.classes.Error && - this.options.quiet - ) { + if (error instanceof Error && this.options.quiet) { this.utils.log.error(error.message); } else { throw error; @@ -634,18 +738,12 @@ class ServerlessAppsyncPlugin { if (!this.options.yes && !(await confirmAction())) { return; } - await this.provider.request< - DeleteDomainNameRequest, - DeleteDomainNameResponse - >('AppSync', 'deleteDomainName', { - domainName: domain.name, - }); + await this.clientFactory + .getAppSyncClient() + .send(new DeleteDomainNameCommand({ domainName: domain.name })); this.utils.log.success(`Domain '${domain.name}' deleted successfully`); } catch (error) { - if ( - error instanceof this.serverless.classes.Error && - this.options.quiet - ) { + if (error instanceof Error && this.options.quiet) { this.utils.log.error(error.message); } else { throw error; @@ -655,19 +753,18 @@ class ServerlessAppsyncPlugin { async getApiAssocStatus(name: string) { try { - const result = await this.provider.request< - GetApiAssociationRequest, - GetApiAssociationResponse - >('AppSync', 'getApiAssociation', { - domainName: name, - }); - return result.apiAssociation; + const result = await this.clientFactory + .getAppSyncClient() + .send(new GetApiAssociationCommand({ domainName: name })); + return result.apiAssociation as + | { associationStatus?: string; apiId?: string } + | undefined; } catch (error) { - if ( - error instanceof this.serverless.classes.Error && - error.providerErrorCodeExtension === 'NOT_FOUND_EXCEPTION' - ) { - return { associationStatus: 'NOT_FOUND' }; + if (error instanceof Error && error.name === 'NotFoundException') { + return { associationStatus: 'NOT_FOUND' } as { + associationStatus?: string; + apiId?: string; + }; } throw error; } @@ -712,13 +809,11 @@ class ServerlessAppsyncPlugin { return; } - await this.provider.request( - 'AppSync', - 'associateApi', - { + await this.clientFactory.getAppSyncClient().send( + new AssociateApiCommand({ domainName: domain.name, apiId, - }, + }), ); const message = `Associating API with domain '${domain.name}'`; @@ -758,12 +853,9 @@ class ServerlessAppsyncPlugin { return; } - await this.provider.request< - DisassociateApiRequest, - DisassociateApiResponse - >('AppSync', 'disassociateApi', { - domainName: domain.name, - }); + await this.clientFactory + .getAppSyncClient() + .send(new DisassociateApiCommand({ domainName: domain.name })); const message = `Disassociating API from domain '${domain.name}'`; await this.showApiAssocStatus({ @@ -782,13 +874,12 @@ class ServerlessAppsyncPlugin { if (domain.hostedZoneId) { return domain.hostedZoneId; } else { - const { HostedZones } = await this.provider.request< - ListHostedZonesByNameRequest, - ListHostedZonesByNameResponse - >('Route53', 'listHostedZonesByName', {}); + const { HostedZones } = await this.clientFactory + .getRoute53Client() + .send(new ListHostedZonesByNameCommand({})); const hostedZoneName = domain.hostedZoneName || getHostedZoneName(domain.name); - const foundHostedZone = HostedZones.find( + const foundHostedZone = HostedZones?.find( (zone) => zone.Name === hostedZoneName, )?.Id; if (!foundHostedZone) { @@ -802,12 +893,9 @@ class ServerlessAppsyncPlugin { async getAppSyncDomainName() { const domain = this.getDomain(); - const { domainNameConfig } = await this.provider.request< - GetDomainNameRequest, - GetDomainNameResponse - >('AppSync', 'getDomainName', { - domainName: domain.name, - }); + const { domainNameConfig } = await this.clientFactory + .getAppSyncClient() + .send(new GetDomainNameCommand({ domainName: domain.name })); const { hostedZoneId, appsyncDomainName: dnsName } = domainNameConfig || {}; if (!hostedZoneId || !dnsName) { @@ -874,13 +962,11 @@ class ServerlessAppsyncPlugin { } async checkRoute53RecordStatus(changeId: string) { - let result: GetChangeResponse; + let result: Record; do { - result = await this.provider.request( - 'Route53', - 'getChange', - { Id: changeId }, - ); + result = await this.clientFactory + .getRoute53Client() + .send(new GetChangeCommand({ Id: changeId })); if (result.ChangeInfo.Status !== 'INSYNC') { await wait(1000); } @@ -898,35 +984,31 @@ class ServerlessAppsyncPlugin { const domain = this.getDomain(); try { - const { ChangeInfo } = await this.provider.request< - ChangeResourceRecordSetsRequest, - ChangeResourceRecordSetsResponse - >('Route53', 'changeResourceRecordSets', { - HostedZoneId: hostedZoneId, - ChangeBatch: { - Changes: [ - { - Action: action, - ResourceRecordSet: { - Name: domain.name, - Type: 'A', - AliasTarget: { - HostedZoneId: domainNamConfig.hostedZoneId, - DNSName: domainNamConfig.dnsName, - EvaluateTargetHealth: false, + const { ChangeInfo } = await this.clientFactory.getRoute53Client().send( + new ChangeResourceRecordSetsCommand({ + HostedZoneId: hostedZoneId, + ChangeBatch: { + Changes: [ + { + Action: action, + ResourceRecordSet: { + Name: domain.name, + Type: 'A', + AliasTarget: { + HostedZoneId: domainNamConfig.hostedZoneId, + DNSName: domainNamConfig.dnsName, + EvaluateTargetHealth: false, + }, }, }, - }, - ], - }, - }); + ], + }, + }), + ); - return ChangeInfo.Id; + return ChangeInfo?.Id; } catch (error) { - if ( - error instanceof this.serverless.classes.Error && - this.options.quiet - ) { + if (error instanceof Error && this.options.quiet) { this.utils.log.error(error.message); } else { throw error; @@ -1066,6 +1148,203 @@ class ServerlessAppsyncPlugin { } }; + async evaluateResolver() { + const { + type, + field, + function: fn, + template, + context: contextArg, + } = this.options; + + // Load context from file or inline JSON + let contextJson: string; + if (!contextArg) { + contextJson = JSON.stringify({}); + } else if (contextArg.trim().startsWith('{')) { + contextJson = contextArg; + } else { + const contextPath = path.resolve(contextArg); + if (!fs.existsSync(contextPath)) { + throw new this.serverless.classes.Error( + `Context file not found: ${contextPath}`, + ); + } + contextJson = fs.readFileSync(contextPath, 'utf8'); + } + + // VTL template evaluation + if (template) { + const templatePath = path.resolve(template); + if (!fs.existsSync(templatePath)) { + throw new this.serverless.classes.Error( + `Template file not found: ${templatePath}`, + ); + } + const templateContent = fs.readFileSync(templatePath, 'utf8'); + + const result = await this.clientFactory.getAppSyncClient().send( + new EvaluateMappingTemplateCommand({ + template: templateContent, + context: contextJson, + }), + ); + + if (result.error) { + this.utils.log.error(`Evaluation error: ${result.error.message}`); + } else { + this.utils.writeText(result.evaluationResult || ''); + } + return; + } + + // JS resolver evaluation — requires type, field, function + if (!type || !field) { + throw new this.serverless.classes.Error( + 'You must specify either --template (VTL) or both --type and --field (JS resolver).', + ); + } + + if (!this.api) { + this.loadConfig(); + } + if (!this.api) { + throw new this.serverless.classes.Error('Could not load the API.'); + } + + const resolverKey = `${type}.${field}`; + const resolverConfig = this.api.config.resolvers[resolverKey]; + if (!resolverConfig) { + throw new this.serverless.classes.Error( + `Resolver '${resolverKey}' not found in configuration.`, + ); + } + + if (resolverConfig.kind !== 'UNIT' || !resolverConfig.code) { + throw new this.serverless.classes.Error( + `Resolver '${resolverKey}' must be a UNIT resolver with a 'code' property for JS evaluation.`, + ); + } + + const codePath = path.resolve(resolverConfig.code); + if (!fs.existsSync(codePath)) { + throw new this.serverless.classes.Error( + `Resolver code file not found: ${codePath}`, + ); + } + let code: string; + if (this.api.config.esbuild === false) { + // esbuild disabled — read the file as-is (plain JS only) + code = fs.readFileSync(codePath, 'utf8'); + } else { + // compile TS/JS through esbuild, same as JsResolver.getResolverContent() + const buildResult = buildSync({ + target: 'esnext', + sourcemap: 'inline', + sourcesContent: false, + treeShaking: true, + ...this.api.config.esbuild, + platform: 'node', + format: 'esm', + entryPoints: [codePath], + bundle: true, + write: false, + external: ['@aws-appsync/utils'], + }); + + if (buildResult.errors.length > 0) { + throw new this.serverless.classes.Error( + `Failed to compile resolver code '${codePath}': ${buildResult.errors[0].text}`, + ); + } + + if (buildResult.outputFiles.length === 0) { + throw new this.serverless.classes.Error( + `Failed to compile resolver code '${codePath}': No output files`, + ); + } + + code = buildResult.outputFiles[0].text; + } + const functionToEval = (fn || 'request') as 'request' | 'response'; + + const result = await this.clientFactory.getAppSyncClient().send( + new EvaluateCodeCommand({ + runtime: { name: RuntimeName.APPSYNC_JS, runtimeVersion: '1.0.0' }, + code, + context: contextJson, + function: functionToEval, + }), + ); + + if (result.logs && result.logs.length > 0) { + this.utils.log.info('Evaluation logs:'); + result.logs.forEach((log) => this.utils.writeText(` ${log}`)); + } + + if (result.error) { + this.utils.log.error(`Evaluation error: ${result.error.message}`); + if (result.error.codeErrors && result.error.codeErrors.length > 0) { + result.error.codeErrors.forEach((ce) => { + this.utils.log.error( + ` ${ce.value} (line ${ce.location?.line}, col ${ce.location?.column})`, + ); + }); + } + } else { + this.utils.writeText(result.evaluationResult || ''); + } + } + + async envGet() { + const apiId = await this.getApiId(); + + const { environmentVariables } = await this.clientFactory + .getAppSyncClient() + .send(new GetGraphqlApiEnvironmentVariablesCommand({ apiId })); + + if ( + !environmentVariables || + Object.keys(environmentVariables).length === 0 + ) { + this.utils.log.info('No environment variables set for this API.'); + return; + } + + const lines = Object.entries(environmentVariables).map( + ([key, value]) => `${key}=${value}`, + ); + this.utils.writeText(lines.join('\n')); + } + + async envSet() { + const { key, value } = this.options; + + if (!key || !value) { + throw new this.serverless.classes.Error( + 'You must specify both --key and --value.', + ); + } + + const apiId = await this.getApiId(); + + // Fetch existing variables first so we do a merge, not a full replace + const { environmentVariables: existing } = await this.clientFactory + .getAppSyncClient() + .send(new GetGraphqlApiEnvironmentVariablesCommand({ apiId })); + + const updated = { ...(existing || {}), [key]: value }; + + await this.clientFactory.getAppSyncClient().send( + new PutGraphqlApiEnvironmentVariablesCommand({ + apiId, + environmentVariables: updated, + }), + ); + + this.utils.log.success(`Environment variable '${key}' set successfully.`); + } + handleConfigValidationError(error: AppSyncValidationError) { const errors = error.validationErrors.map( (error) => ` at appSync${error.path}: ${error.message}`, diff --git a/src/types/serverless.d.ts b/src/types/serverless.d.ts index 3d298c52..f6ffd362 100644 --- a/src/types/serverless.d.ts +++ b/src/types/serverless.d.ts @@ -94,7 +94,6 @@ declare module 'serverless/lib/Serverless' { declare module 'serverless/lib/plugins/aws/provider.js' { import Serverless from 'serverless/lib/Serverless'; - import { ServiceConfigurationOptions } from 'aws-sdk/lib/service'; declare class Provider { constructor(serverless: Serverless); naming: { @@ -105,8 +104,27 @@ declare module 'serverless/lib/plugins/aws/provider.js' { service: string, method: string, params: Input, - options?: ServiceConfigurationOptions, + options?: Record, ) => Promise; + // Resolves the region honoring the --region CLI option, serverless config + // and provider.region (in that order), falling back to us-east-1. + getRegion: () => string; + // Resolves credentials the same way the Serverless Framework does for its + // own AWS calls: default profile, provider.profile, environment creds + // (incl. stage-specific) and the --aws-profile CLI option. The nested + // `credentials` object is an aws-sdk (v2) credentials instance which may + // resolve lazily (shared-ini, SSO, assume-role, MFA). + getCredentials: () => { + credentials?: { + accessKeyId?: string; + secretAccessKey?: string; + sessionToken?: string; + expireTime?: Date | string | number; + getPromise?: () => Promise; + }; + region?: string; + signatureVersion?: string; + }; } export default Provider;