commit 12eb0ff9beafab351dee4c15224f53f2f271035c Author: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Wed Apr 22 14:36:55 2026 -0400 Add door unlock API — Yealink DSS key triggers LenelS2 Elements TemporaryUnlock via API Gateway + Lambda diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b305898 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +cdk.out/ +*.js +*.d.ts +*.js.map diff --git a/bin/app.ts b/bin/app.ts new file mode 100644 index 0000000..943c6bb --- /dev/null +++ b/bin/app.ts @@ -0,0 +1,9 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { DoorUnlockStack } from "../lib/door-unlock-stack"; + +const app = new cdk.App(); +new DoorUnlockStack(app, "SeaHavenDoorUnlockStack", { + env: { account: "328440206208", region: "us-east-1" }, +}); diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..b69b7d0 --- /dev/null +++ b/cdk.json @@ -0,0 +1,22 @@ +{ + "app": "npx ts-node bin/app.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "node_modules", + "test", + "cdk.out" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws"] + } +} diff --git a/door-unlock-plan.md b/door-unlock-plan.md new file mode 100644 index 0000000..0ee2783 --- /dev/null +++ b/door-unlock-plan.md @@ -0,0 +1,237 @@ +# Sea Haven — Desk Phone Door Unlock via LenelS2 Elements + +## Project Plan for Claude Code + +--- + +## Overview + +Build a lightweight AWS middleware that allows a Yealink T58A desk phone to unlock the front door controlled by LenelS2 Elements. The user presses a dedicated button (DSS key) on the Yealink, which fires an HTTPS GET request to an API Gateway endpoint. A Lambda function receives the request, authenticates to the LenelS2 Elements API, and sends a door unlock command. + +**Flow:** `Yealink T58A (DSS key press) → HTTPS GET → API Gateway → Lambda → Elements API → Door Unlocks` + +--- + +## Architecture + +``` +┌──────────────┐ HTTPS GET ┌──────────────────┐ HTTPS POST ┌─────────────────────┐ +│ Yealink T58A │ ───────────────► │ API Gateway │ ──────────────► │ LenelS2 Elements │ +│ DSS Key: │ ?token=xxxxx │ + Lambda │ api-key header │ api.elementssecure │ +│ "Unlock Door"│ │ (us-east-1) │ │ .com │ +└──────────────┘ └──────────────────┘ └─────────────────────┘ + │ + ▼ + ┌──────────────┐ + │ Secrets Mgr │ + │ or SSM Param │ + │ (API keys) │ + └──────────────┘ +``` + +--- + +## Components to Build + +### 1. AWS CDK Stack (TypeScript — matches existing LedgerFlow stack) + +**Stack name:** `SeaHavenDoorUnlockStack` +**Region:** `us-east-1` (matches existing infra) + +#### Resources: +- **API Gateway (HTTP API)** — a single GET endpoint at `/unlock` +- **Lambda function (Node.js 20.x)** — handles the request +- **SSM Parameter Store or Secrets Manager** — stores the Elements API key and the shared bearer token for Yealink auth +- **Optional: Route 53 custom domain** — e.g., `doorunlock.seahaven.com` using the existing wildcard ACM cert and hosted zone + +#### API Gateway Configuration: +- HTTP API (not REST API — simpler, cheaper) +- Single route: `GET /unlock` +- No CORS needed (Yealink sends a plain GET, not a browser request) +- Throttling: 5 requests/second burst, 2 requests/second sustained (matches Elements rate limit of 2 req/sec) + +### 2. Lambda Function Logic + +``` +// Pseudocode for the Lambda handler + +1. Extract query parameter `token` from the request +2. Validate `token` against stored shared secret (from SSM/Secrets Manager) + - If invalid → return 403 +3. Retrieve Elements API key from SSM/Secrets Manager +4. Identify which door/reader to unlock (hardcoded or passed as param) +5. Call the Elements API to execute the door unlock command + - Endpoint: https://api.elementssecure.com/... + - Header: api-key: + - Method: POST (or whatever the Elements API docs specify for door commands) +6. Return 200 with a simple success message (or error details) +``` + +#### Key Design Decisions: +- **Auth from the Yealink:** Use a simple shared secret as a query parameter (`?token=`). The Yealink DSS URL key supports specifying a full URL including query params. HTTPS ensures the token is encrypted in transit. +- **Idempotency:** The door unlock is inherently idempotent (unlocking an already-unlocking door is harmless), so no special handling needed. +- **Logging:** Log every unlock attempt to CloudWatch with timestamp, source IP, and success/failure for audit trail. +- **Rate limiting:** API Gateway throttling prevents abuse. Optionally add a cooldown (e.g., ignore requests within 5 seconds of the last successful unlock). + +### 3. Yealink T58A Configuration + +This is manual config done in the Yealink web UI — not code. Documenting here for completeness. + +1. Log into the Yealink web interface at `http://` +2. Navigate to **DSSKey → Line Key** (or **Programmable Key**) +3. Configure an available key: + - **Type:** URL + - **Label:** Unlock Door + - **Value:** `https://doorunlock.seahaven.com/unlock?token=` +4. Click **Confirm** to save + +**Security note:** Under **Features → General Information**, the Yealink has an "Action URI" trusted IP list. This is for *incoming* action URIs (remote control of the phone). It's not relevant here since the phone is the one *initiating* the HTTP GET. No additional Yealink security config needed for outbound requests. + +--- + +## Prerequisites — Adam Needs to Do Before Claude Code + +### From LenelS2 Elements: + +1. **Enable the Elements API** — Go to the Elements Marketplace and request/enable API access if not already done +2. **Generate an API key** — From the Marketplace, create an API key. This key will be passed as an `api-key` header in requests to `api.elementssecure.com` +3. **Identify the door/reader ID** — In the Elements portal, find the device ID for the front door reader that needs to be unlocked. You'll need this for the API call. +4. **Review the API docs** — Go to `https://api.elementssecure.com/docs` and look for: + - A door unlock or device command endpoint (likely under a `/doors` or `/devices` or `/commands` path) + - The exact HTTP method, URL path, and request body format for a momentary unlock + - Note the exact endpoint path and required parameters +5. **Test the API manually** — Use Postman or curl to confirm you can unlock the door via the API before building the Lambda. Example: + ``` + curl -X POST https://api.elementssecure.com/v1/doors//unlock \ + -H "api-key: " \ + -H "Content-Type: application/json" + ``` + (The exact path/method will come from the API docs — this is just an example) + +### From AWS (should mostly be in place already): + +6. **Confirm the seahaven.com Route 53 hosted zone ID** — for custom domain setup +7. **Confirm the wildcard ACM certificate ARN** — `*.seahaven.com` cert in us-east-1 +8. **Generate a shared secret token** — a long random string (32+ characters) that will be embedded in the Yealink URL and validated by the Lambda. Generate with: `openssl rand -hex 32` + +--- + +## Information to Provide to Claude Code + +When starting the Claude Code session, provide the following: + +``` +Project: Sea Haven Door Unlock Middleware +Stack: AWS CDK (TypeScript), Lambda (Node.js 20.x) +Region: us-east-1 + +Build an AWS CDK stack called SeaHavenDoorUnlockStack that creates: + +1. An HTTP API Gateway with a single GET /unlock route +2. A Lambda function (Node.js 20.x) that: + - Validates a `token` query parameter against a secret stored in SSM Parameter Store + - On valid token, calls the LenelS2 Elements API to unlock the front door + - Elements API base URL: https://api.elementssecure.com + - Elements API auth: pass `api-key` header with the stored API key + - Elements API endpoint for door unlock: [FILL IN from API docs] + - Door/reader ID: [FILL IN from Elements portal] + - Logs all attempts to CloudWatch + - Returns 200 on success, 403 on bad token, 502 on Elements API failure +3. SSM Parameter Store parameters: + - /seahaven/door-unlock/elements-api-key (SecureString) + - /seahaven/door-unlock/auth-token (SecureString) + - /seahaven/door-unlock/door-id (String) +4. Custom domain: doorunlock.seahaven.com + - Route 53 hosted zone: [FILL IN hosted zone ID] + - ACM certificate: [FILL IN wildcard cert ARN] +5. API Gateway throttling: 5 burst, 2 sustained requests/sec + +After deployment, the Yealink phone will call: +GET https://doorunlock.seahaven.com/unlock?token= + +Additional requirements: +- Add a 5-second cooldown between successful unlocks (store last unlock time in Lambda global scope or a simple DynamoDB TTL item) +- Include proper error handling and structured JSON logging +- No external npm dependencies beyond AWS SDK v3 (bundled with Lambda runtime) +``` + +--- + +## Post-Deployment Steps + +1. **Store the secrets in SSM:** + ```bash + aws ssm put-parameter --name "/seahaven/door-unlock/elements-api-key" \ + --value "" --type SecureString --region us-east-1 + + aws ssm put-parameter --name "/seahaven/door-unlock/auth-token" \ + --value "" --type SecureString --region us-east-1 + + aws ssm put-parameter --name "/seahaven/door-unlock/door-id" \ + --value "" --type String --region us-east-1 + ``` + +2. **Test the endpoint:** + ```bash + # Should return 403 + curl https://doorunlock.seahaven.com/unlock?token=wrong-token + + # Should unlock the door + curl https://doorunlock.seahaven.com/unlock?token= + ``` + +3. **Configure the Yealink T58A** (see section above) + +4. **Test end-to-end:** Press the DSS key on the Yealink and verify the door unlocks + +--- + +## Security Considerations + +- **HTTPS everywhere** — Yealink → API Gateway is TLS, Lambda → Elements API is TLS +- **Shared secret in URL** — acceptable because HTTPS encrypts the full URL including query parameters. The token is never logged by API Gateway (use `$context.requestId` not `$context.path` in access logs). Only the Lambda sees the token for validation. +- **Rate limiting** — API Gateway throttle + Lambda cooldown prevent brute force and accidental double-taps +- **IP restriction (optional enhancement)** — if the Yealink phones have static IPs on the LAN and the office has a static public IP, you can add an API Gateway resource policy to restrict access to that IP only. This makes the shared token a second factor rather than the only factor. +- **Audit trail** — every unlock attempt (success or failure) is logged to CloudWatch with timestamp and source IP +- **API key rotation** — the Elements API key and shared token are in SSM Parameter Store, so rotation requires updating the SSM value and (for the shared token) the Yealink DSS key URL + +--- + +## Estimated AWS Cost + +- API Gateway HTTP API: $1.00 per million requests (negligible for door unlocks) +- Lambda: free tier covers 1M requests/month (you'll use maybe 100) +- SSM Parameter Store: free for standard parameters, $0.05/parameter/month for SecureString via advanced +- Route 53: already have the hosted zone ($0.50/month existing) +- **Total incremental cost: effectively $0/month** + +--- + +## Files Claude Code Should Create + +``` +seahaven-door-unlock/ +├── bin/ +│ └── app.ts # CDK app entry point +├── lib/ +│ └── door-unlock-stack.ts # CDK stack definition +├── lambda/ +│ └── unlock-handler.ts # Lambda function code +├── cdk.json +├── tsconfig.json +├── package.json +└── README.md +``` + +--- + +## Open Items (Requires Your Input) + +| Item | What's Needed | Where to Find It | +|------|--------------|-----------------| +| Elements API door unlock endpoint | Exact path, method, and body for unlocking a door | https://api.elementssecure.com/docs | +| Elements door/reader ID | The device ID for your front door | Elements portal → Devices | +| Elements API key | Your API key for auth | Elements Marketplace | +| Route 53 hosted zone ID | Your seahaven.com zone | AWS Console → Route 53 | +| ACM wildcard cert ARN | Your *.seahaven.com cert | AWS Console → ACM (us-east-1) | +| Shared secret token | Generate a random token | Run: `openssl rand -hex 32` | diff --git a/lambda/unlock-handler.ts b/lambda/unlock-handler.ts new file mode 100644 index 0000000..617554a --- /dev/null +++ b/lambda/unlock-handler.ts @@ -0,0 +1,90 @@ +import { + SSMClient, + GetParameterCommand, +} from "@aws-sdk/client-ssm"; + +const ssm = new SSMClient({}); + +let cachedAuthToken: string | undefined; +let cachedApiKey: string | undefined; +let cachedDoorId: string | undefined; +let lastUnlockTime = 0; + +const COOLDOWN_MS = 5_000; + +async function getParameter(name: string, decrypt: boolean): Promise { + const res = await ssm.send( + new GetParameterCommand({ Name: name, WithDecryption: decrypt }) + ); + return res.Parameter!.Value!; +} + +async function loadSecrets() { + const [authToken, apiKey, doorId] = await Promise.all([ + cachedAuthToken ?? getParameter(process.env.AUTH_TOKEN_PARAM!, true), + cachedApiKey ?? getParameter(process.env.ELEMENTS_API_KEY_PARAM!, true), + cachedDoorId ?? getParameter(process.env.DOOR_ID_PARAM!, false), + ]); + cachedAuthToken = authToken; + cachedApiKey = apiKey; + cachedDoorId = doorId; + return { authToken, apiKey, doorId }; +} + +export async function handler(event: { + queryStringParameters?: Record; + requestContext?: { http?: { sourceIp?: string } }; +}) { + const sourceIp = event.requestContext?.http?.sourceIp ?? "unknown"; + const token = event.queryStringParameters?.token; + + if (!token) { + console.log(JSON.stringify({ action: "unlock_attempt", status: "rejected", reason: "missing_token", sourceIp })); + return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; + } + + let secrets; + try { + secrets = await loadSecrets(); + } catch (err) { + console.error(JSON.stringify({ action: "unlock_attempt", status: "error", reason: "ssm_failure", sourceIp, error: String(err) })); + return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) }; + } + + if (token !== secrets.authToken) { + console.log(JSON.stringify({ action: "unlock_attempt", status: "rejected", reason: "invalid_token", sourceIp })); + return { statusCode: 403, body: JSON.stringify({ error: "Forbidden" }) }; + } + + const now = Date.now(); + if (now - lastUnlockTime < COOLDOWN_MS) { + console.log(JSON.stringify({ action: "unlock_attempt", status: "cooldown", sourceIp })); + return { statusCode: 429, body: JSON.stringify({ message: "Cooldown active, try again shortly" }) }; + } + + const url = `https://api.elementssecure.com/v1/devices/${secrets.doorId}/commands/TemporaryUnlock/execute`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "api-key": secrets.apiKey, + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }); + + if (!response.ok) { + const body = await response.text(); + console.error(JSON.stringify({ action: "unlock_attempt", status: "elements_error", statusCode: response.status, body, sourceIp })); + return { statusCode: 502, body: JSON.stringify({ error: "Door system error" }) }; + } + + lastUnlockTime = now; + console.log(JSON.stringify({ action: "unlock_attempt", status: "success", sourceIp })); + return { statusCode: 200, body: JSON.stringify({ message: "Door unlocked" }) }; + } catch (err) { + console.error(JSON.stringify({ action: "unlock_attempt", status: "error", reason: "elements_unreachable", sourceIp, error: String(err) })); + return { statusCode: 502, body: JSON.stringify({ error: "Door system unreachable" }) }; + } +} diff --git a/lib/door-unlock-stack.ts b/lib/door-unlock-stack.ts new file mode 100644 index 0000000..0bea5d3 --- /dev/null +++ b/lib/door-unlock-stack.ts @@ -0,0 +1,125 @@ +import * as cdk from "aws-cdk-lib"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as apigwv2 from "aws-cdk-lib/aws-apigatewayv2"; +import * as integrations from "aws-cdk-lib/aws-apigatewayv2-integrations"; +import * as ssm from "aws-cdk-lib/aws-ssm"; +import * as route53 from "aws-cdk-lib/aws-route53"; +import * as route53Targets from "aws-cdk-lib/aws-route53-targets"; +import * as acm from "aws-cdk-lib/aws-certificatemanager"; +import * as logs from "aws-cdk-lib/aws-logs"; +import { Construct } from "constructs"; +import * as path from "path"; + +export class DoorUnlockStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const elementsApiKeyParam = ssm.StringParameter.fromSecureStringParameterAttributes( + this, + "ElementsApiKey", + { parameterName: "/seahaven/door-unlock/elements-api-key" } + ); + + const authTokenParam = ssm.StringParameter.fromSecureStringParameterAttributes( + this, + "AuthToken", + { parameterName: "/seahaven/door-unlock/auth-token" } + ); + + const doorIdParam = ssm.StringParameter.fromStringParameterName( + this, + "DoorId", + "/seahaven/door-unlock/door-id" + ); + + const unlockHandler = new lambda.Function(this, "UnlockHandler", { + runtime: lambda.Runtime.NODEJS_20_X, + handler: "unlock-handler.handler", + code: lambda.Code.fromAsset(path.join(__dirname, "../lambda"), { + bundling: { + image: lambda.Runtime.NODEJS_20_X.bundlingImage, + local: { + tryBundle(outputDir: string) { + const { execSync } = require("child_process"); + execSync( + `esbuild ${path.join(__dirname, "../lambda/unlock-handler.ts")} --bundle --platform=node --target=node20 --outfile=${path.join(outputDir, "unlock-handler.js")} --external:@aws-sdk/*` + ); + return true; + }, + }, + }, + }), + environment: { + ELEMENTS_API_KEY_PARAM: "/seahaven/door-unlock/elements-api-key", + AUTH_TOKEN_PARAM: "/seahaven/door-unlock/auth-token", + DOOR_ID_PARAM: "/seahaven/door-unlock/door-id", + }, + timeout: cdk.Duration.seconds(10), + memorySize: 128, + logRetention: logs.RetentionDays.THREE_MONTHS, + }); + + elementsApiKeyParam.grantRead(unlockHandler); + authTokenParam.grantRead(unlockHandler); + doorIdParam.grantRead(unlockHandler); + + const httpApi = new apigwv2.HttpApi(this, "DoorUnlockApi", { + apiName: "SeaHavenDoorUnlock", + }); + + const defaultStage = httpApi.defaultStage!.node.defaultChild as apigwv2.CfnStage; + defaultStage.addPropertyOverride("DefaultRouteSettings", { + ThrottlingBurstLimit: 5, + ThrottlingRateLimit: 2, + }); + + httpApi.addRoutes({ + path: "/unlock", + methods: [apigwv2.HttpMethod.GET], + integration: new integrations.HttpLambdaIntegration( + "UnlockIntegration", + unlockHandler + ), + }); + + const hostedZone = route53.HostedZone.fromHostedZoneAttributes( + this, + "SeaHavenZone", + { + hostedZoneId: "Z06652411XKH89KTZD3XA", + zoneName: "seahaven.com", + } + ); + + const certificate = acm.Certificate.fromCertificateArn( + this, + "WildcardCert", + "arn:aws:acm:us-east-1:328440206208:certificate/a66c0994-90d4-410a-a1d9-5595c2a3fae3" + ); + + const domainName = new apigwv2.DomainName(this, "DoorUnlockDomain", { + domainName: "doorunlock.seahaven.com", + certificate, + }); + + new apigwv2.ApiMapping(this, "DoorUnlockMapping", { + api: httpApi, + domainName, + }); + + new route53.ARecord(this, "DoorUnlockARecord", { + zone: hostedZone, + recordName: "doorunlock", + target: route53.RecordTarget.fromAlias( + new route53Targets.ApiGatewayv2DomainProperties( + domainName.regionalDomainName, + domainName.regionalHostedZoneId + ) + ), + }); + + new cdk.CfnOutput(this, "ApiUrl", { + value: `https://doorunlock.seahaven.com/unlock`, + }); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9c59b34 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,984 @@ +{ + "name": "seahaven-door-unlock", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "seahaven-door-unlock", + "version": "1.0.0", + "dependencies": { + "aws-cdk-lib": "^2.178.0", + "constructs": "^10.0.0" + }, + "bin": { + "app": "bin/app.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "aws-cdk": "^2.178.0", + "esbuild": "^0.24.0", + "typescript": "~5.7.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.273", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.273.tgz", + "integrity": "sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz", + "integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "53.17.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.17.0.tgz", + "integrity": "sha512-JB9L4JCbZeYbMJPs7OKV+7YaxKwk4i2tlI5PCSfCzk4DJvprhCO2TYcqbqo3Y1YWZyyY/SEPLWdorG7b88fUSA==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/aws-cdk": { + "version": "2.1118.4", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.4.tgz", + "integrity": "sha512-wJfRQdvb+FJ2cni059mYdmjhfwhMskP+PAB59BL9jhon+jYtjy8X3pbj3uzHgAOJwNhh6jGkP8xq36Cffccbbw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.250.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.250.0.tgz", + "integrity": "sha512-8U8/S9VcmKSc3MHZWiB7P0IecgXoohI8Ya3dgtZMgbzC4mB+MEQmsYBeNgm4vzGQdRos8HjQLnFX1IBlZh7jQA==", + "bundleDependencies": [ + "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.273", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", + "@aws-cdk/cloud-assembly-api": "^2.2.0", + "@aws-cdk/cloud-assembly-schema": "^53.0.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.3", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^10.2.3", + "punycode": "^2.3.1", + "semver": "^7.7.4", + "table": "^6.9.0", + "yaml": "1.10.3" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "constructs": "^10.5.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.2.0", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": ">=53.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.18.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "5.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constructs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", + "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e32119e --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "seahaven-door-unlock", + "version": "1.0.0", + "bin": { + "app": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "cdk": "cdk", + "synth": "cdk synth", + "deploy": "cdk deploy", + "diff": "cdk diff" + }, + "devDependencies": { + "aws-cdk": "^2.178.0", + "typescript": "~5.7.0", + "@types/node": "^22.0.0", + "esbuild": "^0.24.0" + }, + "dependencies": { + "aws-cdk-lib": "^2.178.0", + "constructs": "^10.0.0" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9102eae --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "outDir": "./cdk.out", + "rootDir": ".", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "esModuleInterop": true + }, + "exclude": ["node_modules", "cdk.out"] +}