From bfb9d1fb9b24a27f36d312e315e4e380ce2f18bc Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Nov 2026 15:30:00 +0100 Subject: [PATCH 1/5] feat: add S3 / object storage action New centaurus action exposing AWS S3 (and any S3-compatible provider, via a custom endpoint) as Hercules runtime functions: putObject, getObject, deleteObject, listObjects, and createPresignedUrl. Refs #38 --- .gitlab-ci.yml | 1 + actions/s3-action/.gitignore | 3 + actions/s3-action/Dockerfile | 13 + actions/s3-action/README.md | 41 + actions/s3-action/manifest.json | 10 + actions/s3-action/package-lock.json | 3793 +++++++++++++++++ actions/s3-action/package.json | 26 + .../src/data_types/s3DeleteResult.ts | 16 + actions/s3-action/src/data_types/s3Object.ts | 17 + .../src/data_types/s3ObjectContent.ts | 19 + .../s3-action/src/data_types/s3ObjectList.ts | 24 + .../src/data_types/s3PresignedUrl.ts | 17 + .../functions/createPresignedUrlFunction.ts | 90 + .../src/functions/deleteObjectFunction.ts | 60 + .../src/functions/getObjectFunction.ts | 64 + .../src/functions/listObjectsFunction.ts | 93 + .../src/functions/putObjectFunction.ts | 86 + actions/s3-action/src/helpers.ts | 69 + actions/s3-action/src/index.ts | 114 + actions/s3-action/test/index.test.ts | 109 + actions/s3-action/tsconfig.json | 23 + actions/s3-action/vite.config.ts | 20 + 22 files changed, 4708 insertions(+) create mode 100644 actions/s3-action/.gitignore create mode 100644 actions/s3-action/Dockerfile create mode 100644 actions/s3-action/README.md create mode 100644 actions/s3-action/manifest.json create mode 100644 actions/s3-action/package-lock.json create mode 100644 actions/s3-action/package.json create mode 100644 actions/s3-action/src/data_types/s3DeleteResult.ts create mode 100644 actions/s3-action/src/data_types/s3Object.ts create mode 100644 actions/s3-action/src/data_types/s3ObjectContent.ts create mode 100644 actions/s3-action/src/data_types/s3ObjectList.ts create mode 100644 actions/s3-action/src/data_types/s3PresignedUrl.ts create mode 100644 actions/s3-action/src/functions/createPresignedUrlFunction.ts create mode 100644 actions/s3-action/src/functions/deleteObjectFunction.ts create mode 100644 actions/s3-action/src/functions/getObjectFunction.ts create mode 100644 actions/s3-action/src/functions/listObjectsFunction.ts create mode 100644 actions/s3-action/src/functions/putObjectFunction.ts create mode 100644 actions/s3-action/src/helpers.ts create mode 100644 actions/s3-action/src/index.ts create mode 100644 actions/s3-action/test/index.test.ts create mode 100644 actions/s3-action/tsconfig.json create mode 100644 actions/s3-action/vite.config.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47e94ca..afc6f9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,7 @@ include: - shopware-action - twilio-action - stripe-action + - s3-action .rust-actions: - cron-action diff --git a/actions/s3-action/.gitignore b/actions/s3-action/.gitignore new file mode 100644 index 0000000..ddc3c2a --- /dev/null +++ b/actions/s3-action/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules/ +dist/ diff --git a/actions/s3-action/Dockerfile b/actions/s3-action/Dockerfile new file mode 100644 index 0000000..08c4e6b --- /dev/null +++ b/actions/s3-action/Dockerfile @@ -0,0 +1,13 @@ +FROM node:24-alpine + +WORKDIR /app + +COPY package.json package-lock.json* ./ + +RUN npm ci + +COPY . . + +RUN npm run build + +CMD ["npm", "run", "start"] diff --git a/actions/s3-action/README.md b/actions/s3-action/README.md new file mode 100644 index 0000000..f82e537 --- /dev/null +++ b/actions/s3-action/README.md @@ -0,0 +1,41 @@ +# S3 Action + +S3 / object storage integration for the **Hercules** automation platform. + +Works against AWS S3 and any S3-compatible provider (MinIO, Cloudflare R2, +DigitalOcean Spaces, ...) by pointing the `endpoint` config at that provider. + +## Functions + +| Function | Description | +|----------|-------------| +| `putObject` | Upload a UTF-8 text object, creating or overwriting it. | +| `getObject` | Download an object's content and metadata. | +| `deleteObject` | Delete an object. | +| `listObjects` | List objects in a bucket, optionally filtered by prefix. | +| `createPresignedUrl` | Generate a temporary signed URL for direct GET or PUT access. | + +## Configuration + +| Config | Required | Description | +|--------|----------|-------------| +| `access_key_id` | yes | Access key ID used to authenticate with the S3 API. | +| `secret_access_key` | yes | Secret access key used to authenticate with the S3 API. | +| `region` | no | Region to connect to. Defaults to `us-east-1`. | +| `endpoint` | no | Custom endpoint URL for S3-compatible providers. Empty uses AWS S3. | +| `bucket` | no | Default bucket used when a function call does not specify one. | + +When `endpoint` is set, requests use path-style addressing (required by most +S3-compatible providers); AWS S3 keeps using virtual-hosted-style requests. + +All API calls use the official +[`@aws-sdk/client-s3`](https://www.npmjs.com/package/@aws-sdk/client-s3) and +[`@aws-sdk/s3-request-presigner`](https://www.npmjs.com/package/@aws-sdk/s3-request-presigner) +packages. + +## Data types + +Object content is handled as UTF-8 text (e.g. JSON, CSV, logs, HTML reports), +not arbitrary binary data. The `@Schema` data types are manually defined zod +schemas covering the fields each function needs — there is no vendor OpenAPI +spec for the S3 API to generate from. diff --git a/actions/s3-action/manifest.json b/actions/s3-action/manifest.json new file mode 100644 index 0000000..5687e82 --- /dev/null +++ b/actions/s3-action/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "s3", + "identifier": "s3-action", + "description": "S3 / object storage integration: upload, download, list, and delete objects, and generate presigned URLs, against AWS S3 or any S3-compatible provider.", + "deployment": { + "docker": { + "image": "ghcr.io/code0-tech/centaurus/ci-builds/s3-action" + } + } +} diff --git a/actions/s3-action/package-lock.json b/actions/s3-action/package-lock.json new file mode 100644 index 0000000..aaf27ce --- /dev/null +++ b/actions/s3-action/package-lock.json @@ -0,0 +1,3793 @@ +{ + "name": "@code0-tech/s3-action", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@code0-tech/s3-action", + "version": "1.0.0", + "dependencies": { + "@aws-sdk/client-s3": "^3.700.0", + "@aws-sdk/s3-request-presigner": "^3.700.0", + "@code0-tech/hercules": "^1.1.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.0.0", + "typescript": "^5.9.3", + "vite": "^8.0.3", + "vitest": "^4.1.10" + } + }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1002.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1002.1.tgz", + "integrity": "sha512-CXNESwkQC9jdrUG6cKXBfudVt1frGcYQykcCYbNclVrsewuGMbo3PlFjRteMe4OftNAqhyc3xC2jTz7SUqLasQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1161.0.tgz", + "integrity": "sha512-iRZGMsJnYRrI98O+U6o5fHeLPQTP+Dc/w0mWLcBLQcEQ10DA06+XQj0WmdRiCM7vIso7QEtN1fsj9Vzo5/R3LQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1002.1", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/credential-provider-node": "^3.972.92", + "@aws-sdk/middleware-sdk-s3": "^3.972.85", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.14.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.979.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.979.1.tgz", + "integrity": "sha512-ayOlV1VhLNMiq8PW9ZkVobG4gkldnUoHjG69yy2b8j+VYFlhz/uAL7ZdYQAoQ+eShtGUdNDnnG5rxDg6yUJrOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.975.0", + "@aws-sdk/xml-builder": "^3.972.42", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.38.0", + "@smithy/signature-v4": "^5.7.5", + "@smithy/types": "^4.19.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.80.tgz", + "integrity": "sha512-uO5TiSvb6j1w9RTe5z28OaOmsrOxVQUPaC5+wZl50RfOXcrYD/KgnS8I9lfyGXc7sGbkCopK6VnhNIAoLqfewQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.82.tgz", + "integrity": "sha512-vQzlDHlC24Yz42p+rT4NBoP/5VdetkBoujbOLq9h6QYWC8THuZSPYnXIR9OP3XcOrdMgPBBipOrdssfxnIiWUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.14.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.25.tgz", + "integrity": "sha512-b5xl71uCRdWsu7P1VeGpiGuCf6zfYETsSNOv8AmYVTrsgbvTJRXdmmOPVfxkvGGVNsiKkGctClQW3jAn0SZLug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-login": "^3.972.87", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.87", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.87.tgz", + "integrity": "sha512-C4ZFmcwM+Zj14Z8huVUk54Gv4uzT1ZdzG9cY3Gdh8FePakq7Bss1OFsgZjAhWFOkFOGpBvFBeu0OWjWf8HNKRQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.92", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.92.tgz", + "integrity": "sha512-jC3zmOo7Qxlk+oBivvSsS1Ph7sFGm0QfuhqDGbqgRXi4cxUYMG+dWtBwS2XYYXgc8TIVXJiwgQ65sbNpbYupTA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-ini": "^3.973.25", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.80.tgz", + "integrity": "sha512-xqfixO5aclje7tbQJDias+iNND5wp/3QZixCdZcVQpgWPTZ0V5DfTW9A804A5Z7ezu7sH3WRO+vzUjtiJBA8yg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.24.tgz", + "integrity": "sha512-nlg3BKG03Cgzvv/dMnhxvDJPhAhQSpaN0RUdwgU7ziwkasoev7iI8el8Xy4Rd5sqzgaDUbV269fOXck5SargqA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/token-providers": "3.1161.0", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.86", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.86.tgz", + "integrity": "sha512-zKTyLJBEyfQBVlYqT6Jt+mYV4r8kGbWM7dn6v4Y4MRARizhYafjDXP/iMu76UMRhToSPNmIKX1XAb24gtpBFIA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.85", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.85.tgz", + "integrity": "sha512-Kyl2G6zhIS9qFoeLdlxvXFrWyc6g9D9ZCIRJnGkkfX6JZhxCZB5ebXBc7GKEeetrnhXcail6ALZT0lKbojcaNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.54.tgz", + "integrity": "sha512-/OczlavsS7U5sOodRO10Ug4R/AKFVZiXTstKipWe68EfjPkmdDFNXNl9FCJyXWkg1DG6RBm3qAuVB8FNW9O8KQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.14.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1161.0.tgz", + "integrity": "sha512-ACJ3DWmniBWI4mppBd9ro4aHwF68ImxyZnh/2fgDJHsZesNSByOU2tC/E9uhHX2FPhxrdzfRGnA2REgiNeIwwA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.51.tgz", + "integrity": "sha512-+aHvUugVBuMPXxgkPfWnTMyGdE/yWS8GHi5+qQ37HowS0YqzrbuaDLznj+Dtmtu5jgCdqz36gXmujOToUhNNWw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.975.0", + "@smithy/signature-v4": "^5.7.5", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1161.0.tgz", + "integrity": "sha512-ypbXSofNcSHwf/KA40bFCXJb0MLaAKrfT7sot4dFaA+2lsuXNah4zLRJIAU7Qmvs9PFJ6rnY0bZFEC+mE7attA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.975.0.tgz", + "integrity": "sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.42.tgz", + "integrity": "sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@clack/core": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0-alpha.4.tgz", + "integrity": "sha512-VCtU+vjyKPMSakVrB9q1bOnXN7QW/w4+YQDQCOF59GrzydW+169i0fVx/qzRRXJgt8KGj/pZZ/JxXroFZIDByg==", + "license": "MIT", + "peer": true, + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0-alpha.4.tgz", + "integrity": "sha512-KnmtDF2xQGoI5AlBme9akHtvCRV0RKAARUXHBQO2tMwnY8B08/4zPWigT7uLK25UPrMCEqnyQPkKRjNdhPbf8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clack/core": "1.0.0-alpha.4", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@code0-tech/hercules": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@code0-tech/hercules/-/hercules-1.7.2.tgz", + "integrity": "sha512-xRfolLdKOWC+oiNa7XRo8EeQSylwgq6XkcLhB0YocU8XeEc7qSaHWKJSNnhIDLOmAZzpa7OmpxZj6YFeweUqJg==", + "license": "ISC", + "bin": { + "hercules": "bin/hercules.js" + }, + "peerDependencies": { + "@code0-tech/tucana": "0.0.83", + "@grpc/grpc-js": "^1.14.3", + "@protobuf-ts/grpc-backend": "^2.11.1", + "@protobuf-ts/grpc-transport": "^2.11.1", + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1", + "reflect-metadata": "^0.2.2", + "ts-morph": "^28.0.0", + "ts-to-zod": "^5.1.0", + "zod": "^4.4.3", + "zod-to-ts": "^2.1.0" + } + }, + "node_modules/@code0-tech/tucana": { + "version": "0.0.83", + "resolved": "https://registry.npmjs.org/@code0-tech/tucana/-/tucana-0.0.83.tgz", + "integrity": "sha512-scBOjgf3oIQF8pUlskqPHqfzPvgCJgG1+/tt9RAKWZ1IdCdSqTRG768uEVeTjpuryiGRiO0p8r4bCXNEQNRtRg==", + "license": "MIT", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.7.tgz", + "integrity": "sha512-W4W/fcBMGRVtG6dW2c7cw2mnpS3hvbBd+oQXjh2iYFTZDk1JJmcNqkM5KSaPubQpamlUUurrlb7G1s9NJeERmQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@oclif/core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.14.0.tgz", + "integrity": "sha512-QCJIZoVJxV7jywgVAUlsQwl3dr3Sa21kfi5z9KrYolbexWJUtFSEtMoNiBWojD05mYycLnB50gp/VxlGdaOCRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^6", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.8.1", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.17", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0", + "wsl-utils": "^0.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@protobuf-ts/grpc-backend": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-backend/-/grpc-backend-2.11.1.tgz", + "integrity": "sha512-llAkJ03SlR/+p70YBTMOAQqdkXWghsTWwlPuGXC+Hj99hDQq6fa2CoVePPqbcn7A7/aLBcPG/9Ag3yqlltldxw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.8.22" + } + }, + "node_modules/@protobuf-ts/grpc-transport": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.11.1.tgz", + "integrity": "sha512-l6wrcFffY+tuNnuyrNCkRM8hDIsAZVLA8Mn7PKdVyYxITosYh60qW663p9kL6TWXYuDCL3oxH8ih3vLKTDyhtg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.6.0" + } + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@protobuf-ts/runtime-rpc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", + "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@smithy/core": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.38.1.tgz", + "integrity": "sha512-U/mB2XiwTLaf+NSgLSWwJfptsMT/H8aBi0aTAA/E5e2/TagFeGCqAyhKGG0w0A9IWTkm4qYEmLoD/HB6aYfnRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.14.1.tgz", + "integrity": "sha512-/T6E1jzCL0fErS9uq8JqES/5yYQQ4ppVmmYHwXw2nlov8KFzLb+3QqHR5iAun1BsuAB50IPxZliV8YMgghLwhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.38.1", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.5.tgz", + "integrity": "sha512-WPjdr5oWOk1LArpI4pavu+fSo+/evi0Re1olxGvqwuZopd4gYR1UGCi+Soyn/IjmafqbWs+ZC+3AKxJS3ZpwUg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ts-morph/common": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.29.0.tgz", + "integrity": "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript/vfs": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.5.tgz", + "integrity": "sha512-gobLHQLmvQGJInFk31NvgMd1uYzRXsd8qgnOssyLD055R2QPnw1ubRbBQ4+T+5qC7IQVuYXdvNDE2G7tMDVHgw==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", + "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", + "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.12", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", + "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", + "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.12", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", + "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.12", + "@vitest/utils": "4.1.12", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", + "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", + "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.12", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bowser": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.15.0.tgz", + "integrity": "sha512-xic6KSZglg/I4ymp45fdM4GWfYpBK79Fxd/TOvJktlaPgQqqDxvmGwOPZl2V0dyeS3Lj5c180bLhXF43iyAjZQ==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT", + "peer": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT", + "peer": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/ejs": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT", + "peer": true + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT", + "peer": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "peer": true + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "peer": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT", + "peer": true + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT", + "peer": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT", + "peer": true + }, + "node_modules/rolldown": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT", + "peer": true + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/text-camel-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-camel-case/-/text-camel-case-1.2.11.tgz", + "integrity": "sha512-2ZsM/gOlB1tyza+8lGLvs6gtPuZ9qEYuKPa+gwo38m65wkY4k323SK4hT7ku8r5wIKyspUYIWSk1aB9/Jjxr7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-pascal-case": "^1.2.11" + } + }, + "node_modules/text-capital-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-capital-case/-/text-capital-case-1.2.11.tgz", + "integrity": "sha512-30A7B7+VUvevEmPE0xWK1Z2z0ncl/JTjSUBLfjpoXrkwuPpmNTVbjHShRTN3cX9GIuZn/P3jvR+TO9JiTZcl8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-case/-/text-case-1.2.11.tgz", + "integrity": "sha512-LbdWNQeuXWbfav+pxBxvaefkziffMYeSA53BHp52cgJa9rjiC0dkjum9AKrH8iQWoQJ4InGPSGexLeerGFaZ1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-camel-case": "^1.2.11", + "text-capital-case": "^1.2.11", + "text-constant-case": "^1.2.11", + "text-dot-case": "^1.2.11", + "text-header-case": "^1.2.11", + "text-is-lower-case": "^1.2.11", + "text-is-upper-case": "^1.2.11", + "text-kebab-case": "^1.2.11", + "text-lower-case": "^1.2.11", + "text-lower-case-first": "^1.2.11", + "text-no-case": "^1.2.11", + "text-param-case": "^1.2.11", + "text-pascal-case": "^1.2.11", + "text-path-case": "^1.2.11", + "text-sentence-case": "^1.2.11", + "text-snake-case": "^1.2.11", + "text-swap-case": "^1.2.11", + "text-title-case": "^1.2.11", + "text-upper-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-constant-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-constant-case/-/text-constant-case-1.2.11.tgz", + "integrity": "sha512-XnTBILsa7UpMWncUCchqIybZlg15FUcrlyNaWIJ8ybPy54qcoN513EXFswueyizuAgyJFXPCwwSFbSji6kw/Uw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case": "^1.2.11" + } + }, + "node_modules/text-dot-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-dot-case/-/text-dot-case-1.2.11.tgz", + "integrity": "sha512-7SLKiT45KZO0qad0+p+GvC0+F+6pZ851HJcTcBJiSF88HsK/e1qErlGLtVBT6hkTHIaAj48WfSyQr4lZRv1xJQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-header-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-header-case/-/text-header-case-1.2.11.tgz", + "integrity": "sha512-7OBHd2g7X+aH6rXMC3cANFh6yvhXjXkyumw2NaRwJRIk343pP2e1SQCTCfowPDmmi8wkZVqz1fdWNq5LwvcBOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-capital-case": "^1.2.11" + } + }, + "node_modules/text-is-lower-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-is-lower-case/-/text-is-lower-case-1.2.11.tgz", + "integrity": "sha512-dBqPAkNmX7eTM7ZbS3D/UBCQ5i9EXt5tujF2wIGGbZ1+aN8bY7Qda4mDpxgd6Hbzf/z10uQWRNzupl99wFQ8CQ==", + "license": "MIT", + "peer": true + }, + "node_modules/text-is-upper-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-is-upper-case/-/text-is-upper-case-1.2.11.tgz", + "integrity": "sha512-MZeUIYEYfKZ2FSeg0vnHCH4mHXLgGzes+iz2K+4BYnhnkEa2svKA1nNjQAqTUiVNHOPqPCuzmUr1LsyQZ73uyA==", + "license": "MIT", + "peer": true + }, + "node_modules/text-kebab-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-kebab-case/-/text-kebab-case-1.2.11.tgz", + "integrity": "sha512-RIg9iN6VwH+JrX9dFdm1nd1efPGR9LjNc0CiQz496sQETeKGkDEzxES/ZzxbkerrAL2DFEMGdLXckzDz1OEDBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-lower-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-lower-case/-/text-lower-case-1.2.11.tgz", + "integrity": "sha512-txTy6y0y8M23Lhf0mk8WcvXTqlf4OQ3AGnDsRB6o3uMNfIa0CJDol2s1PdKNa63rt5B2277zkZCCn6Xeq//big==", + "license": "MIT", + "peer": true + }, + "node_modules/text-lower-case-first": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-lower-case-first/-/text-lower-case-first-1.2.11.tgz", + "integrity": "sha512-QR483XLyuyIpq8tKu1ds3Q1jfsgfaa/p9rtoQKHe6Rv5ah9ic/SUzTGN0MQ7UIS9APADd8SUPn5TTh1Z2/ACyg==", + "license": "MIT", + "peer": true + }, + "node_modules/text-no-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-no-case/-/text-no-case-1.2.11.tgz", + "integrity": "sha512-wazS7FEq0Ct3aJzeE8MEMcSs0eW4+/X/fwdotv/rG66bLS+g1T0pa0gUsbBGjjLFs191AIXVIry+bYE0uaaBBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-lower-case": "^1.2.11" + } + }, + "node_modules/text-param-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-param-case/-/text-param-case-1.2.11.tgz", + "integrity": "sha512-3EMMAMLSz/mJXOnATNnrS+dZAvghpq09VhOVYDOkUnbm5zlYc6iU5AZOKVDpiAVVllQ9P1h5IKVZzsEYrdIRGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-pascal-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-pascal-case/-/text-pascal-case-1.2.11.tgz", + "integrity": "sha512-BNhQ1O/g/Q4dH5gPyLIJLDLDknl2dipBwV629ScsiZCKJaCLGXYhTXp23rp9Htg3O5OSSsiU3mqDKq+pBmwTSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-path-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-path-case/-/text-path-case-1.2.11.tgz", + "integrity": "sha512-FsJU4BmMdtLtmnBK/XRJPqTwLF8yFiTEClHjxlQjSAG5Xt9R4p6D1WNaM1CI2dG5Lr4rsFM4jiVC620m0AsRbw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-sentence-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-sentence-case/-/text-sentence-case-1.2.11.tgz", + "integrity": "sha512-ApiVsvdLy+Wb8x7mZRVuoy8VO12jJ22G2djVM3ZZbUhXVIkqGgHxmiXRwdhRPoWGojK9n53m7jviJgBVNdRn+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-snake-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-snake-case/-/text-snake-case-1.2.11.tgz", + "integrity": "sha512-NOEQvjyyVABB41SS8dUx423Y6hWS+Z4TrAAJg1xzCkOD3q9y0JtdJjCvCA1FWI8oDu+HiIOp/N446uDM8j54XQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-swap-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-swap-case/-/text-swap-case-1.2.11.tgz", + "integrity": "sha512-PBmC5xvZdDZ4suikydpeXH0s4JV2XHelMj9/OEXEbA3oLpdV2A+B4BspVDWVw7C2Gi5eCareqk/7EE8I1/WwgQ==", + "license": "MIT", + "peer": true + }, + "node_modules/text-title-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-title-case/-/text-title-case-1.2.11.tgz", + "integrity": "sha512-V1GZy0XlqdkYUQm0tqm1jqtYlXJqFVMreBCTUReOaz8d/JbozTSpZrcakIeV8+1bN7LsvfhPFhA5zREiax6YIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-upper-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-upper-case/-/text-upper-case-1.2.11.tgz", + "integrity": "sha512-BfTL7yB1YIRlVGNdZUvno013hOq2cRs07fDR2ApppOXRDuKrEmsLDEY82xXlDzQHELp0jexqkI+NeyPIl6MtMw==", + "license": "MIT", + "peer": true + }, + "node_modules/text-upper-case-first": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-upper-case-first/-/text-upper-case-first-1.2.11.tgz", + "integrity": "sha512-vgfbwKo8TEJbRsapR9LWWvIJRnv8u9aXVa6cyYOAQQmurCx54Cnt59x5fKdiq+hFaBJ51AbzCgMpbP3p65/pHQ==", + "license": "MIT", + "peer": true + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-morph": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-28.0.0.tgz", + "integrity": "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ts-morph/common": "~0.29.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/ts-to-zod": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ts-to-zod/-/ts-to-zod-5.1.0.tgz", + "integrity": "sha512-giqqlvRHunlJqG9tBL/KAO3wWIVZGF//mZiWLKm/fdQnKnz4EN2mtiK5cugN9slytBkdMEXQIaLvMzIScbhhFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clack/prompts": "1.0.0-alpha.4", + "@oclif/core": "^4.5.4", + "@typescript/vfs": "^1.5.0", + "chokidar": "^4.0.3", + "listr2": "^9.0.4", + "slash": "^5.1.0", + "text-case": "^1.2.4", + "tslib": "^2.3.1", + "tsutils": "^3.21.0", + "typescript": "^5.2.2", + "zod": "^4.1.5" + }, + "bin": { + "ts-to-zod": "bin/run" + } + }, + "node_modules/tslib": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", + "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD", + "peer": true + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "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==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", + "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.12", + "@vitest/mocker": "4.1.12", + "@vitest/pretty-format": "4.1.12", + "@vitest/runner": "4.1.12", + "@vitest/snapshot": "4.1.12", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.12", + "@vitest/browser-preview": "4.1.12", + "@vitest/browser-webdriverio": "^4.1.11", + "@vitest/coverage-istanbul": "4.1.12", + "@vitest/coverage-v8": "4.1.12", + "@vitest/ui": "4.1.12", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT", + "peer": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wsl-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.4.0.tgz", + "integrity": "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-ts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-2.1.0.tgz", + "integrity": "sha512-jZP1GokTqR99FLmtGU+B9acjD9u/R++b++Vxe1sWpBQDd82/9/j5LyLZZ7/Oy3h2nxcz5NihikgX4D0hhdu3+g==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "typescript": "^5 || ^6", + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/actions/s3-action/package.json b/actions/s3-action/package.json new file mode 100644 index 0000000..9d164f2 --- /dev/null +++ b/actions/s3-action/package.json @@ -0,0 +1,26 @@ +{ + "name": "@code0-tech/s3-action", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "typecheck": "tsc --noEmit", + "build": "vite build", + "test": "vitest run", + "start": "node dist/index.js" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.700.0", + "@aws-sdk/s3-request-presigner": "^3.700.0", + "@code0-tech/hercules": "^1.1.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.0.0", + "typescript": "^5.9.3", + "vite": "^8.0.3", + "vitest": "^4.1.10" + } +} diff --git a/actions/s3-action/src/data_types/s3DeleteResult.ts b/actions/s3-action/src/data_types/s3DeleteResult.ts new file mode 100644 index 0000000..62a6a67 --- /dev/null +++ b/actions/s3-action/src/data_types/s3DeleteResult.ts @@ -0,0 +1,16 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +export const S3DeleteResultSchema = z.object({ + Key: z.string(), + Bucket: z.string(), + DeleteMarker: z.boolean().optional(), + VersionId: z.string().optional(), +}); +export type S3DeleteResult = z.infer; + +@Identifier("S3_DELETE_RESULT") +@Name({ code: "en-US", content: "S3 delete result" }) +@DisplayMessage({ code: "en-US", content: "Deleted S3 object ${Key}" }) +@Schema(S3DeleteResultSchema) +export class S3DeleteResultDataType {} diff --git a/actions/s3-action/src/data_types/s3Object.ts b/actions/s3-action/src/data_types/s3Object.ts new file mode 100644 index 0000000..e686e89 --- /dev/null +++ b/actions/s3-action/src/data_types/s3Object.ts @@ -0,0 +1,17 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +export const S3ObjectSchema = z.object({ + Key: z.string(), + Bucket: z.string(), + ETag: z.string().optional(), + Location: z.string(), + VersionId: z.string().optional(), +}); +export type S3Object = z.infer; + +@Identifier("S3_OBJECT") +@Name({ code: "en-US", content: "S3 object" }) +@DisplayMessage({ code: "en-US", content: "S3 object ${Key}" }) +@Schema(S3ObjectSchema) +export class S3ObjectDataType {} diff --git a/actions/s3-action/src/data_types/s3ObjectContent.ts b/actions/s3-action/src/data_types/s3ObjectContent.ts new file mode 100644 index 0000000..089c545 --- /dev/null +++ b/actions/s3-action/src/data_types/s3ObjectContent.ts @@ -0,0 +1,19 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +export const S3ObjectContentSchema = z.object({ + Key: z.string(), + Bucket: z.string(), + Content: z.string(), + ContentType: z.string().optional(), + ContentLength: z.number().optional(), + ETag: z.string().optional(), + LastModified: z.string().optional(), +}); +export type S3ObjectContent = z.infer; + +@Identifier("S3_OBJECT_CONTENT") +@Name({ code: "en-US", content: "S3 object content" }) +@DisplayMessage({ code: "en-US", content: "S3 object content ${Key}" }) +@Schema(S3ObjectContentSchema) +export class S3ObjectContentDataType {} diff --git a/actions/s3-action/src/data_types/s3ObjectList.ts b/actions/s3-action/src/data_types/s3ObjectList.ts new file mode 100644 index 0000000..9215346 --- /dev/null +++ b/actions/s3-action/src/data_types/s3ObjectList.ts @@ -0,0 +1,24 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +export const S3ObjectListSchema = z.object({ + Bucket: z.string(), + Prefix: z.string().optional(), + Objects: z.array( + z.object({ + Key: z.string(), + Size: z.number().optional(), + ETag: z.string().optional(), + LastModified: z.string().optional(), + }) + ), + IsTruncated: z.boolean().optional(), + NextContinuationToken: z.string().optional(), +}); +export type S3ObjectList = z.infer; + +@Identifier("S3_OBJECT_LIST") +@Name({ code: "en-US", content: "S3 object list" }) +@DisplayMessage({ code: "en-US", content: "S3 objects in ${Bucket}" }) +@Schema(S3ObjectListSchema) +export class S3ObjectListDataType {} diff --git a/actions/s3-action/src/data_types/s3PresignedUrl.ts b/actions/s3-action/src/data_types/s3PresignedUrl.ts new file mode 100644 index 0000000..4dc15dc --- /dev/null +++ b/actions/s3-action/src/data_types/s3PresignedUrl.ts @@ -0,0 +1,17 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +export const S3PresignedUrlSchema = z.object({ + Url: z.string(), + Key: z.string(), + Bucket: z.string(), + Operation: z.enum(["get", "put"]), + ExpiresInSeconds: z.number(), +}); +export type S3PresignedUrl = z.infer; + +@Identifier("S3_PRESIGNED_URL") +@Name({ code: "en-US", content: "S3 presigned URL" }) +@DisplayMessage({ code: "en-US", content: "Presigned URL for ${Key}" }) +@Schema(S3PresignedUrlSchema) +export class S3PresignedUrlDataType {} diff --git a/actions/s3-action/src/functions/createPresignedUrlFunction.ts b/actions/s3-action/src/functions/createPresignedUrlFunction.ts new file mode 100644 index 0000000..b42c759 --- /dev/null +++ b/actions/s3-action/src/functions/createPresignedUrlFunction.ts @@ -0,0 +1,90 @@ +import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { S3PresignedUrl, S3PresignedUrlSchema } from "../data_types/s3PresignedUrl.js"; + +@Identifier("createPresignedUrl") +@DisplayIcon("simple:amazons3") +@Signature('(Key: string, Operation?: "get"|"put", ExpiresInSeconds?: number, Bucket?: string): S3_PRESIGNED_URL') +@Name({ code: "en-US", content: "Create presigned URL" }) +@DisplayMessage({ code: "en-US", content: "Create presigned URL for ${Key}" }) +@Documentation({ + code: "en-US", + content: + "Generates a temporary, signed URL that grants direct GET or PUT access to an object without sharing the configured credentials. The URL expires after ExpiresInSeconds (default 900, i.e. 15 minutes).", +}) +@Description({ + code: "en-US", + content: "Generates a temporary presigned URL for an object.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to grant access to within the bucket." }], +}) +@Parameter({ + runtimeName: "Operation", + name: [{ code: "en-US", content: "Operation" }], + description: [{ code: "en-US", content: "Whether the URL grants read ('get') or write ('put') access. Defaults to 'get'." }], + optional: true, +}) +@Parameter({ + runtimeName: "ExpiresInSeconds", + name: [{ code: "en-US", content: "Expires in (seconds)" }], + description: [{ code: "en-US", content: "How long the URL stays valid, in seconds. Defaults to 900 (15 minutes)." }], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket the object lives in. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class CreatePresignedUrlFunction { + async run( + context: FunctionContext, + Key: string, + Operation?: "get" | "put", + ExpiresInSeconds?: number, + Bucket?: string + ): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + const operation = Operation ?? "get"; + const expiresIn = ExpiresInSeconds ?? 900; + + if (operation !== "get" && operation !== "put") { + throw new RuntimeError("S3_INVALID_PRESIGNED_URL_OPERATION", `Operation must be 'get' or 'put', got '${operation}'.`); + } + + try { + const command = + operation === "get" ? new GetObjectCommand({ Bucket: bucket, Key }) : new PutObjectCommand({ Bucket: bucket, Key }); + const url = await getSignedUrl(client, command, { expiresIn }); + + return S3PresignedUrlSchema.parse({ + Url: url, + Key, + Bucket: bucket, + Operation: operation, + ExpiresInSeconds: expiresIn, + }); + } catch (error) { + throw toRuntimeError("S3_CREATE_PRESIGNED_URL_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/deleteObjectFunction.ts b/actions/s3-action/src/functions/deleteObjectFunction.ts new file mode 100644 index 0000000..ee14c11 --- /dev/null +++ b/actions/s3-action/src/functions/deleteObjectFunction.ts @@ -0,0 +1,60 @@ +import { DeleteObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { S3DeleteResult, S3DeleteResultSchema } from "../data_types/s3DeleteResult.js"; + +@Identifier("deleteObject") +@DisplayIcon("simple:amazons3") +@Signature("(Key: string, Bucket?: string): S3_DELETE_RESULT") +@Name({ code: "en-US", content: "Delete object" }) +@DisplayMessage({ code: "en-US", content: "Delete object ${Key}" }) +@Documentation({ + code: "en-US", + content: "Deletes an object from S3. Deleting a key that does not exist is not an error.", +}) +@Description({ + code: "en-US", + content: "Deletes an object from S3.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to delete within the bucket." }], +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to delete from. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class DeleteObjectFunction { + async run(context: FunctionContext, Key: string, Bucket?: string): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send(new DeleteObjectCommand({ Bucket: bucket, Key })); + + return S3DeleteResultSchema.parse({ + Key, + Bucket: bucket, + DeleteMarker: result.DeleteMarker, + VersionId: result.VersionId, + }); + } catch (error) { + throw toRuntimeError("S3_DELETE_OBJECT_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/getObjectFunction.ts b/actions/s3-action/src/functions/getObjectFunction.ts new file mode 100644 index 0000000..aeb1a9b --- /dev/null +++ b/actions/s3-action/src/functions/getObjectFunction.ts @@ -0,0 +1,64 @@ +import { GetObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { S3ObjectContent, S3ObjectContentSchema } from "../data_types/s3ObjectContent.js"; + +@Identifier("getObject") +@DisplayIcon("simple:amazons3") +@Signature("(Key: string, Bucket?: string): S3_OBJECT_CONTENT") +@Name({ code: "en-US", content: "Download object" }) +@DisplayMessage({ code: "en-US", content: "Download object ${Key}" }) +@Documentation({ + code: "en-US", + content: "Downloads an object from S3 and returns its content as UTF-8 text alongside its metadata.", +}) +@Description({ + code: "en-US", + content: "Downloads an object from S3.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to read within the bucket." }], +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to read from. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class GetObjectFunction { + async run(context: FunctionContext, Key: string, Bucket?: string): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key })); + const content = await result.Body?.transformToString("utf-8"); + + return S3ObjectContentSchema.parse({ + Key, + Bucket: bucket, + Content: content ?? "", + ContentType: result.ContentType, + ContentLength: result.ContentLength, + ETag: result.ETag, + LastModified: result.LastModified?.toISOString(), + }); + } catch (error) { + throw toRuntimeError("S3_GET_OBJECT_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/listObjectsFunction.ts b/actions/s3-action/src/functions/listObjectsFunction.ts new file mode 100644 index 0000000..c8f5119 --- /dev/null +++ b/actions/s3-action/src/functions/listObjectsFunction.ts @@ -0,0 +1,93 @@ +import { ListObjectsV2Command } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { S3ObjectList, S3ObjectListSchema } from "../data_types/s3ObjectList.js"; + +@Identifier("listObjects") +@DisplayIcon("simple:amazons3") +@Signature("(Prefix?: string, Bucket?: string, MaxKeys?: number, ContinuationToken?: string): S3_OBJECT_LIST") +@Name({ code: "en-US", content: "List objects" }) +@DisplayMessage({ code: "en-US", content: "List objects in ${Bucket}" }) +@Documentation({ + code: "en-US", + content: + "Lists objects in a bucket, optionally filtered by key prefix. Results are paginated; pass the returned NextContinuationToken back in as ContinuationToken to fetch the next page.", +}) +@Description({ + code: "en-US", + content: "Lists objects in a bucket.", +}) +@Parameter({ + runtimeName: "Prefix", + name: [{ code: "en-US", content: "Prefix" }], + description: [{ code: "en-US", content: "Only return objects whose key starts with this prefix." }], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to list. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +@Parameter({ + runtimeName: "MaxKeys", + name: [{ code: "en-US", content: "Max keys" }], + description: [{ code: "en-US", content: "The maximum number of keys to return in one page. Defaults to 1000." }], + optional: true, +}) +@Parameter({ + runtimeName: "ContinuationToken", + name: [{ code: "en-US", content: "Continuation token" }], + description: [{ code: "en-US", content: "Token from a previous call's NextContinuationToken, to fetch the next page." }], + optional: true, +}) +export class ListObjectsFunction { + async run( + context: FunctionContext, + Prefix?: string, + Bucket?: string, + MaxKeys?: number, + ContinuationToken?: string + ): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + ...(Prefix ? { Prefix } : {}), + ...(MaxKeys ? { MaxKeys } : {}), + ...(ContinuationToken ? { ContinuationToken } : {}), + }) + ); + + return S3ObjectListSchema.parse({ + Bucket: bucket, + Prefix, + Objects: (result.Contents ?? []).map((object) => ({ + Key: object.Key, + Size: object.Size, + ETag: object.ETag, + LastModified: object.LastModified?.toISOString(), + })), + IsTruncated: result.IsTruncated, + NextContinuationToken: result.NextContinuationToken, + }); + } catch (error) { + throw toRuntimeError("S3_LIST_OBJECTS_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/putObjectFunction.ts b/actions/s3-action/src/functions/putObjectFunction.ts new file mode 100644 index 0000000..aa91392 --- /dev/null +++ b/actions/s3-action/src/functions/putObjectFunction.ts @@ -0,0 +1,86 @@ +import { PutObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; + +@Identifier("putObject") +@DisplayIcon("simple:amazons3") +@Signature("(Key: string, Content: string, ContentType?: string, Bucket?: string): S3_OBJECT") +@Name({ code: "en-US", content: "Upload object" }) +@DisplayMessage({ code: "en-US", content: "Upload object ${Key}" }) +@Documentation({ + code: "en-US", + content: + "Uploads a UTF-8 text object to S3 (or an S3-compatible provider), creating it or overwriting an existing object with the same key.", +}) +@Description({ + code: "en-US", + content: "Uploads an object to S3.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to write to within the bucket." }], +}) +@Parameter({ + runtimeName: "Content", + name: [{ code: "en-US", content: "Content" }], + description: [{ code: "en-US", content: "The UTF-8 text content to store as the object body." }], +}) +@Parameter({ + runtimeName: "ContentType", + name: [{ code: "en-US", content: "Content type" }], + description: [{ code: "en-US", content: "The MIME type of the content, e.g. application/json or text/plain." }], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to upload to. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class PutObjectFunction { + async run( + context: FunctionContext, + Key: string, + Content: string, + ContentType?: string, + Bucket?: string + ): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send( + new PutObjectCommand({ + Bucket: bucket, + Key, + Body: Content, + ...(ContentType ? { ContentType } : {}), + }) + ); + + return S3ObjectSchema.parse({ + Key, + Bucket: bucket, + ETag: result.ETag, + Location: `${bucket}/${Key}`, + VersionId: result.VersionId, + }); + } catch (error) { + throw toRuntimeError("S3_PUT_OBJECT_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/helpers.ts b/actions/s3-action/src/helpers.ts new file mode 100644 index 0000000..ecc2b5b --- /dev/null +++ b/actions/s3-action/src/helpers.ts @@ -0,0 +1,69 @@ +import { S3Client } from "@aws-sdk/client-s3"; +import { FunctionContext, RuntimeError } from "@code0-tech/hercules"; + +/** + * Builds (and caches) an S3 client from the action configuration. + * + * The client is cached per (accessKeyId, region, endpoint) combination so + * repeated function invocations reuse the same instance and connection pool. + * Uses the official `@aws-sdk/client-s3`, which works against AWS S3 and any + * S3-compatible provider (MinIO, Cloudflare R2, DigitalOcean Spaces, ...) by + * pointing `endpoint` at that provider. + */ +let cached: { accessKeyId: string; region: string; endpoint: string; client: S3Client } | null = null; + +export function getS3Client(context: FunctionContext): S3Client { + const accessKeyId = context.matchedConfig.findConfig("access_key_id") as string; + const secretAccessKey = context.matchedConfig.findConfig("secret_access_key") as string; + + if (!accessKeyId || !secretAccessKey) { + throw new RuntimeError( + "S3_MISSING_CREDENTIALS", + "No S3 credentials configured. Set the 'access_key_id' and 'secret_access_key' configs." + ); + } + + const region = (context.matchedConfig.findConfig("region") as string) || "us-east-1"; + const endpoint = (context.matchedConfig.findConfig("endpoint") as string) || ""; + + if (cached && cached.accessKeyId === accessKeyId && cached.region === region && cached.endpoint === endpoint) { + return cached.client; + } + + const client = new S3Client({ + region, + credentials: { accessKeyId, secretAccessKey }, + // A custom endpoint is only set for S3-compatible providers (MinIO, + // R2, Spaces, ...), which virtually always require path-style + // addressing. Real AWS S3 keeps using virtual-hosted-style requests. + ...(endpoint ? { endpoint, forcePathStyle: true } : {}), + }); + cached = { accessKeyId, region, endpoint, client }; + return client; +} + +/** + * Resolves the bucket to operate on: an explicit per-call override, falling + * back to the action's configured default bucket. + */ +export function resolveBucket(context: FunctionContext, bucket?: string): string { + const resolved = bucket || (context.matchedConfig.findConfig("bucket") as string) || ""; + if (!resolved) { + throw new RuntimeError( + "S3_MISSING_BUCKET", + "No bucket provided. Pass a Bucket value or configure a default 'bucket' for the action." + ); + } + return resolved; +} + +/** + * Maps an error thrown by the AWS SDK (or anything else) into a Hercules + * RuntimeError with a stable code and a human readable message. + */ +export function toRuntimeError(code: string, error: unknown): RuntimeError { + if (error instanceof Error) { + return new RuntimeError(code, error.message); + } + return new RuntimeError(code, "An unexpected error occurred while calling the S3 API."); +} diff --git a/actions/s3-action/src/index.ts b/actions/s3-action/src/index.ts new file mode 100644 index 0000000..f28822a --- /dev/null +++ b/actions/s3-action/src/index.ts @@ -0,0 +1,114 @@ +import "reflect-metadata"; +import { Action, CodeZeroEvent } from "@code0-tech/hercules"; + +import { S3ObjectDataType } from "./data_types/s3Object.js"; +import { S3ObjectContentDataType } from "./data_types/s3ObjectContent.js"; +import { S3DeleteResultDataType } from "./data_types/s3DeleteResult.js"; +import { S3ObjectListDataType } from "./data_types/s3ObjectList.js"; +import { S3PresignedUrlDataType } from "./data_types/s3PresignedUrl.js"; + +import { PutObjectFunction } from "./functions/putObjectFunction.js"; +import { GetObjectFunction } from "./functions/getObjectFunction.js"; +import { DeleteObjectFunction } from "./functions/deleteObjectFunction.js"; +import { ListObjectsFunction } from "./functions/listObjectsFunction.js"; +import { CreatePresignedUrlFunction } from "./functions/createPresignedUrlFunction.js"; + +const action = new Action( + process.env.ACTION_ID ?? "s3-action", + process.env.VERSION ?? "1.0.0", + process.env.AQUILA_URL ?? "127.0.0.1:8081", + "CodeZero", + "simple:amazons3", + "S3 / object storage integration: upload, download, list, and delete objects, and generate presigned URLs, against AWS S3 or any S3-compatible provider.", + [{ code: "en-US", content: "S3" }], + [ + { + identifier: "access_key_id", + type: "TEXT", + name: [{ code: "en-US", content: "Access key ID" }], + description: [{ code: "en-US", content: "The access key ID used to authenticate with the S3 API." }], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "secret_access_key", + type: "TEXT", + name: [{ code: "en-US", content: "Secret access key" }], + description: [{ code: "en-US", content: "The secret access key used to authenticate with the S3 API." }], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "region", + type: "TEXT", + defaultValue: "us-east-1", + name: [{ code: "en-US", content: "Region" }], + description: [ + { + code: "en-US", + content: "The region to connect to, e.g. us-east-1. S3-compatible providers may require a placeholder such as 'auto'.", + }, + ], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "endpoint", + type: "TEXT", + defaultValue: "", + name: [{ code: "en-US", content: "Endpoint" }], + description: [ + { + code: "en-US", + content: + "Optional custom endpoint URL for S3-compatible providers (MinIO, Cloudflare R2, DigitalOcean Spaces, ...). Leave empty to use AWS S3.", + }, + ], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "bucket", + type: "TEXT", + defaultValue: "", + name: [{ code: "en-US", content: "Default bucket" }], + description: [ + { code: "en-US", content: "Optional default bucket used when a function call does not specify one." }, + ], + linkedDataTypes: ["TEXT"], + }, + ] +); + +action.registerDataTypeClass(S3ObjectDataType); +action.registerDataTypeClass(S3ObjectContentDataType); +action.registerDataTypeClass(S3DeleteResultDataType); +action.registerDataTypeClass(S3ObjectListDataType); +action.registerDataTypeClass(S3PresignedUrlDataType); + +action.registerRuntimeFunction(PutObjectFunction); +action.registerRuntimeFunction(GetObjectFunction); +action.registerRuntimeFunction(DeleteObjectFunction); +action.registerRuntimeFunction(ListObjectsFunction); +action.registerRuntimeFunction(CreatePresignedUrlFunction); + +action.on(CodeZeroEvent.connected, () => { + console.log("Connected to aquila"); +}); + +action.on(CodeZeroEvent.error, (error: Error) => { + console.error("Stream error:", error.message); + console.log("Attempting to reconnect in 5s..."); + setTimeout(() => { + action.connect(process.env.AUTH_TOKEN ?? "your_auth_token_here").catch((err: Error) => { + action.emit(CodeZeroEvent.error, err); + }); + }, 5000); +}); + +action.connect(process.env.AUTH_TOKEN ?? "your_auth_token_here").catch((err: Error) => { + action.emit(CodeZeroEvent.error, err); +}); + +action.on(CodeZeroEvent.moduleUpdated, (message: any) => { + console.dir(message, { depth: null }); + console.dir(action.configs.values(), { depth: null }); +}); + +export { action }; diff --git a/actions/s3-action/test/index.test.ts b/actions/s3-action/test/index.test.ts new file mode 100644 index 0000000..2c395b0 --- /dev/null +++ b/actions/s3-action/test/index.test.ts @@ -0,0 +1,109 @@ +import "reflect-metadata"; +import { describe, expect, it } from "vitest"; +import type { FunctionContext } from "@code0-tech/hercules"; +import { RuntimeError } from "@code0-tech/hercules"; +import { S3ObjectSchema } from "../src/data_types/s3Object.js"; +import { S3ObjectContentSchema } from "../src/data_types/s3ObjectContent.js"; +import { S3DeleteResultSchema } from "../src/data_types/s3DeleteResult.js"; +import { S3ObjectListSchema } from "../src/data_types/s3ObjectList.js"; +import { S3PresignedUrlSchema } from "../src/data_types/s3PresignedUrl.js"; +import { getS3Client, resolveBucket } from "../src/helpers.js"; + +function makeContext(configValues: Record): FunctionContext { + return { + projectId: 1, + executionId: "test-execution", + matchedConfig: { + projectId: 1, + configValues: Object.entries(configValues).map(([identifier, value]) => ({ identifier, value: value as any })), + findConfig: (identifier: string) => configValues[identifier] as any, + }, + executeFlow: async () => null, + }; +} + +describe("S3ObjectSchema", () => { + it("parses a representative put-object result", () => { + const parsed = S3ObjectSchema.parse({ + Key: "reports/2026-11-01.json", + Bucket: "my-bucket", + ETag: '"d41d8cd98f00b204e9800998ecf8427e"', + Location: "my-bucket/reports/2026-11-01.json", + }); + expect(parsed.Key).toEqual("reports/2026-11-01.json"); + }); +}); + +describe("S3ObjectContentSchema", () => { + it("parses a representative get-object result", () => { + const parsed = S3ObjectContentSchema.parse({ + Key: "reports/2026-11-01.json", + Bucket: "my-bucket", + Content: '{"status":"ok"}', + ContentType: "application/json", + ContentLength: 16, + }); + expect(parsed.Content).toEqual('{"status":"ok"}'); + }); +}); + +describe("S3DeleteResultSchema", () => { + it("parses a representative delete result", () => { + const parsed = S3DeleteResultSchema.parse({ Key: "obsolete.txt", Bucket: "my-bucket" }); + expect(parsed.Bucket).toEqual("my-bucket"); + }); +}); + +describe("S3ObjectListSchema", () => { + it("parses a representative list-objects result", () => { + const parsed = S3ObjectListSchema.parse({ + Bucket: "my-bucket", + Prefix: "reports/", + Objects: [{ Key: "reports/2026-11-01.json", Size: 16 }], + IsTruncated: false, + }); + expect(parsed.Objects).toHaveLength(1); + }); +}); + +describe("S3PresignedUrlSchema", () => { + it("parses a representative presigned-url result", () => { + const parsed = S3PresignedUrlSchema.parse({ + Url: "https://my-bucket.s3.amazonaws.com/key?X-Amz-Signature=...", + Key: "reports/2026-11-01.json", + Bucket: "my-bucket", + Operation: "get", + ExpiresInSeconds: 900, + }); + expect(parsed.Operation).toEqual("get"); + }); +}); + +describe("resolveBucket", () => { + it("prefers an explicit bucket override", () => { + const context = makeContext({ bucket: "default-bucket" }); + expect(resolveBucket(context, "override-bucket")).toEqual("override-bucket"); + }); + + it("falls back to the configured default bucket", () => { + const context = makeContext({ bucket: "default-bucket" }); + expect(resolveBucket(context)).toEqual("default-bucket"); + }); + + it("throws when no bucket is available", () => { + const context = makeContext({}); + expect(() => resolveBucket(context)).toThrow(RuntimeError); + }); +}); + +describe("getS3Client", () => { + it("throws when credentials are not configured", () => { + const context = makeContext({}); + expect(() => getS3Client(context)).toThrow(RuntimeError); + }); + + it("builds a client when credentials are configured", () => { + const context = makeContext({ access_key_id: "AKIA...", secret_access_key: "secret" }); + expect(() => getS3Client(context)).not.toThrow(); + }); +}); diff --git a/actions/s3-action/tsconfig.json b/actions/s3-action/tsconfig.json new file mode 100644 index 0000000..65d780f --- /dev/null +++ b/actions/s3-action/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "declaration": true, + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "baseUrl": ".", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": ["node", "vite/client"] + }, + "include": [ + "src/**/*", + "test/**/*", + "vite.config.ts" + ] +} diff --git a/actions/s3-action/vite.config.ts b/actions/s3-action/vite.config.ts new file mode 100644 index 0000000..ae8b973 --- /dev/null +++ b/actions/s3-action/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + build: { + target: "node18", + ssr: resolve(__dirname, 'src/index.ts'), + rollupOptions: { + external: (id) => + [ + 'fs', + 'path', + 'typescript', + '@code0-tech/hercules', + '@aws-sdk/client-s3', + '@aws-sdk/s3-request-presigner' + ].includes(id) || id.startsWith('node:') + } + } +}); From ba2af0fc2af8dfe43103e320f78ce3c0f19a5b63 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Nov 2026 15:31:36 +0100 Subject: [PATCH 2/5] fix: key the S3 client cache on secret access key too Avoids returning a stale client with the old secret after credential rotation that keeps the same access key ID, region, and endpoint. Refs #38 --- actions/s3-action/src/helpers.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/actions/s3-action/src/helpers.ts b/actions/s3-action/src/helpers.ts index ecc2b5b..b942144 100644 --- a/actions/s3-action/src/helpers.ts +++ b/actions/s3-action/src/helpers.ts @@ -4,13 +4,16 @@ import { FunctionContext, RuntimeError } from "@code0-tech/hercules"; /** * Builds (and caches) an S3 client from the action configuration. * - * The client is cached per (accessKeyId, region, endpoint) combination so - * repeated function invocations reuse the same instance and connection pool. + * The client is cached per (accessKeyId, secretAccessKey, region, endpoint) + * combination so repeated function invocations reuse the same instance and + * connection pool, while a credential or region change (e.g. key rotation) + * still produces a fresh client instead of silently reusing a stale one. * Uses the official `@aws-sdk/client-s3`, which works against AWS S3 and any * S3-compatible provider (MinIO, Cloudflare R2, DigitalOcean Spaces, ...) by * pointing `endpoint` at that provider. */ -let cached: { accessKeyId: string; region: string; endpoint: string; client: S3Client } | null = null; +let cached: { accessKeyId: string; secretAccessKey: string; region: string; endpoint: string; client: S3Client } | null = + null; export function getS3Client(context: FunctionContext): S3Client { const accessKeyId = context.matchedConfig.findConfig("access_key_id") as string; @@ -26,7 +29,13 @@ export function getS3Client(context: FunctionContext): S3Client { const region = (context.matchedConfig.findConfig("region") as string) || "us-east-1"; const endpoint = (context.matchedConfig.findConfig("endpoint") as string) || ""; - if (cached && cached.accessKeyId === accessKeyId && cached.region === region && cached.endpoint === endpoint) { + if ( + cached && + cached.accessKeyId === accessKeyId && + cached.secretAccessKey === secretAccessKey && + cached.region === region && + cached.endpoint === endpoint + ) { return cached.client; } @@ -38,7 +47,7 @@ export function getS3Client(context: FunctionContext): S3Client { // addressing. Real AWS S3 keeps using virtual-hosted-style requests. ...(endpoint ? { endpoint, forcePathStyle: true } : {}), }); - cached = { accessKeyId, region, endpoint, client }; + cached = { accessKeyId, secretAccessKey, region, endpoint, client }; return client; } From 539996b4cca66e50df5911142292909e3f8b6f6e Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Nov 2026 16:00:41 +0100 Subject: [PATCH 3/5] feat: use the standard FILE type and optional credentials in s3-action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings two design decisions over from an earlier WIP attempt at this action (origin/#38-s3-action, same author, never finished — it doesn't typecheck against the current hercules SDK): - putObject/getObject now exchange content as hercules' standard FILE value (base64 + content type) instead of UTF-8 text only, so binary objects work and files interop with other flow functions. - access_key_id/secret_access_key are now optional, falling back to the AWS SDK's default credential provider chain (env vars, shared config, or an IAM role) when unset. Also adds StorageClass to listObjects results and an explicit Deleted flag to deleteObject's result, both present in that WIP. Refs #38 --- actions/s3-action/README.md | 23 +++++++++---- .../src/data_types/s3DeleteResult.ts | 1 + .../src/data_types/s3ObjectContent.ts | 10 ++++-- .../s3-action/src/data_types/s3ObjectList.ts | 1 + .../src/functions/deleteObjectFunction.ts | 1 + .../src/functions/getObjectFunction.ts | 13 +++++--- .../src/functions/listObjectsFunction.ts | 1 + .../src/functions/putObjectFunction.ts | 33 ++++++++----------- actions/s3-action/src/helpers.ts | 21 ++++++------ actions/s3-action/src/index.ts | 18 ++++++++-- actions/s3-action/test/index.test.ts | 22 +++++++++---- 11 files changed, 90 insertions(+), 54 deletions(-) diff --git a/actions/s3-action/README.md b/actions/s3-action/README.md index f82e537..42a712d 100644 --- a/actions/s3-action/README.md +++ b/actions/s3-action/README.md @@ -9,22 +9,31 @@ DigitalOcean Spaces, ...) by pointing the `endpoint` config at that provider. | Function | Description | |----------|-------------| -| `putObject` | Upload a UTF-8 text object, creating or overwriting it. | +| `putObject` | Upload an object, creating or overwriting it. | | `getObject` | Download an object's content and metadata. | | `deleteObject` | Delete an object. | | `listObjects` | List objects in a bucket, optionally filtered by prefix. | | `createPresignedUrl` | Generate a temporary signed URL for direct GET or PUT access. | +Object content is exchanged as hercules' standard `FILE` value (base64 content +plus an optional file name/content type), the same type other actions/functions +in a flow produce and consume — so a file downloaded from S3 can be piped +directly into, say, an HTTP upload, and vice versa. + ## Configuration | Config | Required | Description | |--------|----------|-------------| -| `access_key_id` | yes | Access key ID used to authenticate with the S3 API. | -| `secret_access_key` | yes | Secret access key used to authenticate with the S3 API. | +| `access_key_id` | no | Access key ID. Leave empty to fall back to the AWS SDK's default credential provider chain (env vars, shared config file, or an IAM role). | +| `secret_access_key` | no | Secret access key. Leave empty for the same default credential provider chain fallback. | | `region` | no | Region to connect to. Defaults to `us-east-1`. | | `endpoint` | no | Custom endpoint URL for S3-compatible providers. Empty uses AWS S3. | | `bucket` | no | Default bucket used when a function call does not specify one. | +Leaving both `access_key_id` and `secret_access_key` empty is the expected +setup when this action runs inside AWS (EC2/ECS/EKS) with an IAM role, rather +than a static key pair. + When `endpoint` is set, requests use path-style addressing (required by most S3-compatible providers); AWS S3 keeps using virtual-hosted-style requests. @@ -35,7 +44,7 @@ packages. ## Data types -Object content is handled as UTF-8 text (e.g. JSON, CSV, logs, HTML reports), -not arbitrary binary data. The `@Schema` data types are manually defined zod -schemas covering the fields each function needs — there is no vendor OpenAPI -spec for the S3 API to generate from. +The `@Schema` data types are manually defined zod schemas covering the fields +each function needs — there is no vendor OpenAPI spec for the S3 API to +generate from. `S3_OBJECT_CONTENT`'s `File` field reuses hercules' built-in +`fileSchema`/`FILE` data type rather than inventing a bespoke one. diff --git a/actions/s3-action/src/data_types/s3DeleteResult.ts b/actions/s3-action/src/data_types/s3DeleteResult.ts index 62a6a67..f061ccf 100644 --- a/actions/s3-action/src/data_types/s3DeleteResult.ts +++ b/actions/s3-action/src/data_types/s3DeleteResult.ts @@ -4,6 +4,7 @@ import { z } from "zod"; export const S3DeleteResultSchema = z.object({ Key: z.string(), Bucket: z.string(), + Deleted: z.boolean(), DeleteMarker: z.boolean().optional(), VersionId: z.string().optional(), }); diff --git a/actions/s3-action/src/data_types/s3ObjectContent.ts b/actions/s3-action/src/data_types/s3ObjectContent.ts index 089c545..9e6f747 100644 --- a/actions/s3-action/src/data_types/s3ObjectContent.ts +++ b/actions/s3-action/src/data_types/s3ObjectContent.ts @@ -1,11 +1,15 @@ -import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { DisplayMessage, fileSchema, Identifier, Name, Schema } from "@code0-tech/hercules"; import { z } from "zod"; +/** + * File carries the downloaded body as a std FILE value (base64 content plus + * file name/content type) from hercules' `taurus-file` module, so it can be + * passed directly into any other function/action that accepts a FILE. + */ export const S3ObjectContentSchema = z.object({ Key: z.string(), Bucket: z.string(), - Content: z.string(), - ContentType: z.string().optional(), + File: fileSchema, ContentLength: z.number().optional(), ETag: z.string().optional(), LastModified: z.string().optional(), diff --git a/actions/s3-action/src/data_types/s3ObjectList.ts b/actions/s3-action/src/data_types/s3ObjectList.ts index 9215346..82e5b22 100644 --- a/actions/s3-action/src/data_types/s3ObjectList.ts +++ b/actions/s3-action/src/data_types/s3ObjectList.ts @@ -10,6 +10,7 @@ export const S3ObjectListSchema = z.object({ Size: z.number().optional(), ETag: z.string().optional(), LastModified: z.string().optional(), + StorageClass: z.string().optional(), }) ), IsTruncated: z.boolean().optional(), diff --git a/actions/s3-action/src/functions/deleteObjectFunction.ts b/actions/s3-action/src/functions/deleteObjectFunction.ts index ee14c11..35e09f5 100644 --- a/actions/s3-action/src/functions/deleteObjectFunction.ts +++ b/actions/s3-action/src/functions/deleteObjectFunction.ts @@ -50,6 +50,7 @@ export class DeleteObjectFunction { return S3DeleteResultSchema.parse({ Key, Bucket: bucket, + Deleted: true, DeleteMarker: result.DeleteMarker, VersionId: result.VersionId, }); diff --git a/actions/s3-action/src/functions/getObjectFunction.ts b/actions/s3-action/src/functions/getObjectFunction.ts index aeb1a9b..2c531ab 100644 --- a/actions/s3-action/src/functions/getObjectFunction.ts +++ b/actions/s3-action/src/functions/getObjectFunction.ts @@ -20,7 +20,8 @@ import { S3ObjectContent, S3ObjectContentSchema } from "../data_types/s3ObjectCo @DisplayMessage({ code: "en-US", content: "Download object ${Key}" }) @Documentation({ code: "en-US", - content: "Downloads an object from S3 and returns its content as UTF-8 text alongside its metadata.", + content: + "Downloads an object from S3 and returns its content as a standard FILE value (base64-encoded content plus content type), so it can be passed directly to other functions/actions that accept a FILE.", }) @Description({ code: "en-US", @@ -46,13 +47,17 @@ export class GetObjectFunction { try { const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key })); - const content = await result.Body?.transformToString("utf-8"); + const bytes = (await result.Body?.transformToByteArray()) ?? new Uint8Array(); return S3ObjectContentSchema.parse({ Key, Bucket: bucket, - Content: content ?? "", - ContentType: result.ContentType, + File: { + contentType: result.ContentType, + fileName: Key.split("/").pop() || Key, + valueType: "base64", + value: Buffer.from(bytes).toString("base64"), + }, ContentLength: result.ContentLength, ETag: result.ETag, LastModified: result.LastModified?.toISOString(), diff --git a/actions/s3-action/src/functions/listObjectsFunction.ts b/actions/s3-action/src/functions/listObjectsFunction.ts index c8f5119..df1e25d 100644 --- a/actions/s3-action/src/functions/listObjectsFunction.ts +++ b/actions/s3-action/src/functions/listObjectsFunction.ts @@ -82,6 +82,7 @@ export class ListObjectsFunction { Size: object.Size, ETag: object.ETag, LastModified: object.LastModified?.toISOString(), + StorageClass: object.StorageClass, })), IsTruncated: result.IsTruncated, NextContinuationToken: result.NextContinuationToken, diff --git a/actions/s3-action/src/functions/putObjectFunction.ts b/actions/s3-action/src/functions/putObjectFunction.ts index aa91392..d39f7a3 100644 --- a/actions/s3-action/src/functions/putObjectFunction.ts +++ b/actions/s3-action/src/functions/putObjectFunction.ts @@ -1,4 +1,5 @@ import { PutObjectCommand } from "@aws-sdk/client-s3"; +import type { File } from "@code0-tech/hercules"; import { Description, DisplayIcon, @@ -15,13 +16,13 @@ import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; @Identifier("putObject") @DisplayIcon("simple:amazons3") -@Signature("(Key: string, Content: string, ContentType?: string, Bucket?: string): S3_OBJECT") +@Signature("(Key: string, File: FILE, Bucket?: string): S3_OBJECT") @Name({ code: "en-US", content: "Upload object" }) @DisplayMessage({ code: "en-US", content: "Upload object ${Key}" }) @Documentation({ code: "en-US", content: - "Uploads a UTF-8 text object to S3 (or an S3-compatible provider), creating it or overwriting an existing object with the same key.", + "Uploads an object to S3 (or an S3-compatible provider), creating it or overwriting an existing object with the same key. `File` is a standard FILE value (base64-encoded content plus an optional content type).", }) @Description({ code: "en-US", @@ -33,15 +34,9 @@ import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; description: [{ code: "en-US", content: "The object key (path) to write to within the bucket." }], }) @Parameter({ - runtimeName: "Content", - name: [{ code: "en-US", content: "Content" }], - description: [{ code: "en-US", content: "The UTF-8 text content to store as the object body." }], -}) -@Parameter({ - runtimeName: "ContentType", - name: [{ code: "en-US", content: "Content type" }], - description: [{ code: "en-US", content: "The MIME type of the content, e.g. application/json or text/plain." }], - optional: true, + runtimeName: "File", + name: [{ code: "en-US", content: "File" }], + description: [{ code: "en-US", content: "The file content to upload, as a standard FILE value." }], }) @Parameter({ runtimeName: "Bucket", @@ -52,23 +47,21 @@ import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; optional: true, }) export class PutObjectFunction { - async run( - context: FunctionContext, - Key: string, - Content: string, - ContentType?: string, - Bucket?: string - ): Promise { + async run(context: FunctionContext, Key: string, File: File, Bucket?: string): Promise { const bucket = resolveBucket(context, Bucket); const client = getS3Client(context); + const body = Buffer.from(File.value, "base64"); + // `File.contentType` is generic/unknown at the schema level (hercules' + // FILE value doesn't parameterize it), but it's a string at runtime. + const contentType = typeof File.contentType === "string" ? File.contentType : undefined; try { const result = await client.send( new PutObjectCommand({ Bucket: bucket, Key, - Body: Content, - ...(ContentType ? { ContentType } : {}), + Body: body, + ...(contentType ? { ContentType: contentType } : {}), }) ); diff --git a/actions/s3-action/src/helpers.ts b/actions/s3-action/src/helpers.ts index b942144..e72a589 100644 --- a/actions/s3-action/src/helpers.ts +++ b/actions/s3-action/src/helpers.ts @@ -15,17 +15,16 @@ import { FunctionContext, RuntimeError } from "@code0-tech/hercules"; let cached: { accessKeyId: string; secretAccessKey: string; region: string; endpoint: string; client: S3Client } | null = null; +/** + * access_key_id / secret_access_key are optional: when either is left + * unconfigured, the AWS SDK's default credential provider chain is used + * instead (environment variables, shared config/credentials file, or an + * IAM role), which is the common setup when this action runs inside AWS + * (EC2/ECS/EKS) rather than with a static key pair. + */ export function getS3Client(context: FunctionContext): S3Client { - const accessKeyId = context.matchedConfig.findConfig("access_key_id") as string; - const secretAccessKey = context.matchedConfig.findConfig("secret_access_key") as string; - - if (!accessKeyId || !secretAccessKey) { - throw new RuntimeError( - "S3_MISSING_CREDENTIALS", - "No S3 credentials configured. Set the 'access_key_id' and 'secret_access_key' configs." - ); - } - + const accessKeyId = (context.matchedConfig.findConfig("access_key_id") as string) || ""; + const secretAccessKey = (context.matchedConfig.findConfig("secret_access_key") as string) || ""; const region = (context.matchedConfig.findConfig("region") as string) || "us-east-1"; const endpoint = (context.matchedConfig.findConfig("endpoint") as string) || ""; @@ -41,7 +40,7 @@ export function getS3Client(context: FunctionContext): S3Client { const client = new S3Client({ region, - credentials: { accessKeyId, secretAccessKey }, + ...(accessKeyId && secretAccessKey ? { credentials: { accessKeyId, secretAccessKey } } : {}), // A custom endpoint is only set for S3-compatible providers (MinIO, // R2, Spaces, ...), which virtually always require path-style // addressing. Real AWS S3 keeps using virtual-hosted-style requests. diff --git a/actions/s3-action/src/index.ts b/actions/s3-action/src/index.ts index f28822a..19a6ce0 100644 --- a/actions/s3-action/src/index.ts +++ b/actions/s3-action/src/index.ts @@ -25,15 +25,29 @@ const action = new Action( { identifier: "access_key_id", type: "TEXT", + optional: true, name: [{ code: "en-US", content: "Access key ID" }], - description: [{ code: "en-US", content: "The access key ID used to authenticate with the S3 API." }], + description: [ + { + code: "en-US", + content: + "The access key ID used to authenticate with the S3 API. Leave empty to fall back to the AWS SDK's default credential provider chain (environment variables, shared config file, or an IAM role).", + }, + ], linkedDataTypes: ["TEXT"], }, { identifier: "secret_access_key", type: "TEXT", + optional: true, name: [{ code: "en-US", content: "Secret access key" }], - description: [{ code: "en-US", content: "The secret access key used to authenticate with the S3 API." }], + description: [ + { + code: "en-US", + content: + "The secret access key used to authenticate with the S3 API. Leave empty to fall back to the AWS SDK's default credential provider chain.", + }, + ], linkedDataTypes: ["TEXT"], }, { diff --git a/actions/s3-action/test/index.test.ts b/actions/s3-action/test/index.test.ts index 2c395b0..8d240d1 100644 --- a/actions/s3-action/test/index.test.ts +++ b/actions/s3-action/test/index.test.ts @@ -35,22 +35,27 @@ describe("S3ObjectSchema", () => { }); describe("S3ObjectContentSchema", () => { - it("parses a representative get-object result", () => { + it("parses a representative get-object result with a base64 FILE value", () => { const parsed = S3ObjectContentSchema.parse({ Key: "reports/2026-11-01.json", Bucket: "my-bucket", - Content: '{"status":"ok"}', - ContentType: "application/json", + File: { + contentType: "application/json", + fileName: "2026-11-01.json", + valueType: "base64", + value: Buffer.from('{"status":"ok"}').toString("base64"), + }, ContentLength: 16, }); - expect(parsed.Content).toEqual('{"status":"ok"}'); + expect(Buffer.from(parsed.File.value, "base64").toString("utf-8")).toEqual('{"status":"ok"}'); }); }); describe("S3DeleteResultSchema", () => { it("parses a representative delete result", () => { - const parsed = S3DeleteResultSchema.parse({ Key: "obsolete.txt", Bucket: "my-bucket" }); + const parsed = S3DeleteResultSchema.parse({ Key: "obsolete.txt", Bucket: "my-bucket", Deleted: true }); expect(parsed.Bucket).toEqual("my-bucket"); + expect(parsed.Deleted).toBe(true); }); }); @@ -97,9 +102,12 @@ describe("resolveBucket", () => { }); describe("getS3Client", () => { - it("throws when credentials are not configured", () => { + it("builds a client via the default credential provider chain when none are configured", () => { + // access_key_id/secret_access_key are optional: with neither set, the + // AWS SDK falls back to its default credential provider chain (env + // vars, shared config, or an IAM role) instead of throwing. const context = makeContext({}); - expect(() => getS3Client(context)).toThrow(RuntimeError); + expect(() => getS3Client(context)).not.toThrow(); }); it("builds a client when credentials are configured", () => { From e2e5c7a2a5da05c7a842538c08cc167f5ecf0e49 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Nov 2026 18:42:28 +0100 Subject: [PATCH 4/5] feat: support multi-file upload in s3 putObject Address review feedback on #85: - putObject now accepts `Files: FILE<"*/*">[]` and uploads each file, returning `S3_OBJECT[]`. Each object's key is derived from the file's own name, optionally under an optional `KeyPrefix` (folder). - Fix the FILE parameter type: `FILE` -> `FILE<"*/*">`. - Add a `resolveObjectKey` helper (prefix + file name joining) with tests. Co-Authored-By: Claude Opus 4.8 --- actions/s3-action/README.md | 2 +- .../src/functions/putObjectFunction.ts | 81 ++++++++++++------- actions/s3-action/src/helpers.ts | 22 +++++ actions/s3-action/test/index.test.ts | 20 ++++- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/actions/s3-action/README.md b/actions/s3-action/README.md index 42a712d..aca4beb 100644 --- a/actions/s3-action/README.md +++ b/actions/s3-action/README.md @@ -9,7 +9,7 @@ DigitalOcean Spaces, ...) by pointing the `endpoint` config at that provider. | Function | Description | |----------|-------------| -| `putObject` | Upload an object, creating or overwriting it. | +| `putObject` | Upload one or more objects, creating or overwriting them. Each object's key is the file's own name, optionally under a `KeyPrefix`. | | `getObject` | Download an object's content and metadata. | | `deleteObject` | Delete an object. | | `listObjects` | List objects in a bucket, optionally filtered by prefix. | diff --git a/actions/s3-action/src/functions/putObjectFunction.ts b/actions/s3-action/src/functions/putObjectFunction.ts index d39f7a3..1e85ada 100644 --- a/actions/s3-action/src/functions/putObjectFunction.ts +++ b/actions/s3-action/src/functions/putObjectFunction.ts @@ -11,32 +11,44 @@ import { Parameter, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; +import { getS3Client, resolveBucket, resolveObjectKey, toRuntimeError } from "../helpers.js"; import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; @Identifier("putObject") @DisplayIcon("simple:amazons3") -@Signature("(Key: string, File: FILE, Bucket?: string): S3_OBJECT") -@Name({ code: "en-US", content: "Upload object" }) -@DisplayMessage({ code: "en-US", content: "Upload object ${Key}" }) +@Signature('(Files: FILE<"*/*">[], KeyPrefix?: string, Bucket?: string): S3_OBJECT[]') +@Name({ code: "en-US", content: "Upload objects" }) +@DisplayMessage({ code: "en-US", content: "Upload objects under ${KeyPrefix}" }) @Documentation({ code: "en-US", content: - "Uploads an object to S3 (or an S3-compatible provider), creating it or overwriting an existing object with the same key. `File` is a standard FILE value (base64-encoded content plus an optional content type).", + "Uploads one or more objects to S3 (or an S3-compatible provider), creating them or overwriting existing objects with the same key. Each object's key is the uploaded file's own name, optionally placed under `KeyPrefix` (e.g. `reports/`). `Files` are standard FILE values (base64-encoded content plus a file name and optional content type).", }) @Description({ code: "en-US", - content: "Uploads an object to S3.", + content: "Uploads one or more objects to S3.", }) @Parameter({ - runtimeName: "Key", - name: [{ code: "en-US", content: "Key" }], - description: [{ code: "en-US", content: "The object key (path) to write to within the bucket." }], + runtimeName: "Files", + name: [{ code: "en-US", content: "Files" }], + description: [ + { + code: "en-US", + content: "The files to upload, as standard FILE values. Each file's name becomes its object key.", + }, + ], }) @Parameter({ - runtimeName: "File", - name: [{ code: "en-US", content: "File" }], - description: [{ code: "en-US", content: "The file content to upload, as a standard FILE value." }], + runtimeName: "KeyPrefix", + name: [{ code: "en-US", content: "Key prefix" }], + description: [ + { + code: "en-US", + content: + "Optional key prefix (folder) the files are uploaded under, e.g. `reports/`. Joined to each file name with a `/`. Omit to upload at the bucket root under each file's name.", + }, + ], + optional: true, }) @Parameter({ runtimeName: "Bucket", @@ -47,31 +59,38 @@ import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; optional: true, }) export class PutObjectFunction { - async run(context: FunctionContext, Key: string, File: File, Bucket?: string): Promise { + async run(context: FunctionContext, Files: File<"*/*">[], KeyPrefix?: string, Bucket?: string): Promise { const bucket = resolveBucket(context, Bucket); const client = getS3Client(context); - const body = Buffer.from(File.value, "base64"); - // `File.contentType` is generic/unknown at the schema level (hercules' - // FILE value doesn't parameterize it), but it's a string at runtime. - const contentType = typeof File.contentType === "string" ? File.contentType : undefined; + const files = Files ?? []; try { - const result = await client.send( - new PutObjectCommand({ - Bucket: bucket, - Key, - Body: body, - ...(contentType ? { ContentType: contentType } : {}), + return await Promise.all( + files.map(async (file) => { + const key = resolveObjectKey(file.fileName, KeyPrefix); + const body = Buffer.from(file.value, "base64"); + // `file.contentType` is generic/unknown at the schema level (hercules' + // FILE value doesn't parameterize it), but it's a string at runtime. + const contentType = typeof file.contentType === "string" ? file.contentType : undefined; + + const result = await client.send( + new PutObjectCommand({ + Bucket: bucket, + Key: key, + Body: body, + ...(contentType ? { ContentType: contentType } : {}), + }) + ); + + return S3ObjectSchema.parse({ + Key: key, + Bucket: bucket, + ETag: result.ETag, + Location: `${bucket}/${key}`, + VersionId: result.VersionId, + }); }) ); - - return S3ObjectSchema.parse({ - Key, - Bucket: bucket, - ETag: result.ETag, - Location: `${bucket}/${Key}`, - VersionId: result.VersionId, - }); } catch (error) { throw toRuntimeError("S3_PUT_OBJECT_FAILED", error); } diff --git a/actions/s3-action/src/helpers.ts b/actions/s3-action/src/helpers.ts index e72a589..6e20de3 100644 --- a/actions/s3-action/src/helpers.ts +++ b/actions/s3-action/src/helpers.ts @@ -65,6 +65,28 @@ export function resolveBucket(context: FunctionContext, bucket?: string): string return resolved; } +/** + * Builds the object key for an uploaded file. The key is the file's own + * `fileName` (the FILE value always carries one), optionally placed under a + * key prefix so several files can be uploaded into the same "folder". The + * prefix and file name are joined with a single `/` unless the prefix is empty + * or already ends with one. + */ +export function resolveObjectKey(fileName: string, prefix?: string): string { + const name = fileName || ""; + if (!name) { + throw new RuntimeError( + "S3_MISSING_FILE_NAME", + "A file to upload is missing its file name; cannot derive an object key for it." + ); + } + const normalizedPrefix = prefix || ""; + if (!normalizedPrefix) { + return name; + } + return normalizedPrefix.endsWith("/") ? `${normalizedPrefix}${name}` : `${normalizedPrefix}/${name}`; +} + /** * Maps an error thrown by the AWS SDK (or anything else) into a Hercules * RuntimeError with a stable code and a human readable message. diff --git a/actions/s3-action/test/index.test.ts b/actions/s3-action/test/index.test.ts index 8d240d1..27a7b8a 100644 --- a/actions/s3-action/test/index.test.ts +++ b/actions/s3-action/test/index.test.ts @@ -7,7 +7,7 @@ import { S3ObjectContentSchema } from "../src/data_types/s3ObjectContent.js"; import { S3DeleteResultSchema } from "../src/data_types/s3DeleteResult.js"; import { S3ObjectListSchema } from "../src/data_types/s3ObjectList.js"; import { S3PresignedUrlSchema } from "../src/data_types/s3PresignedUrl.js"; -import { getS3Client, resolveBucket } from "../src/helpers.js"; +import { getS3Client, resolveBucket, resolveObjectKey } from "../src/helpers.js"; function makeContext(configValues: Record): FunctionContext { return { @@ -101,6 +101,24 @@ describe("resolveBucket", () => { }); }); +describe("resolveObjectKey", () => { + it("uses the file name alone when no prefix is given", () => { + expect(resolveObjectKey("report.json")).toEqual("report.json"); + }); + + it("joins a prefix and file name with a single slash", () => { + expect(resolveObjectKey("report.json", "reports")).toEqual("reports/report.json"); + }); + + it("does not double the slash when the prefix already ends with one", () => { + expect(resolveObjectKey("report.json", "reports/")).toEqual("reports/report.json"); + }); + + it("throws when the file has no name to derive a key from", () => { + expect(() => resolveObjectKey("", "reports/")).toThrow(RuntimeError); + }); +}); + describe("getS3Client", () => { it("builds a client via the default credential provider chain when none are configured", () => { // access_key_id/secret_access_key are optional: with neither set, the From ebc48e2d71d7a34801b10e4f31d86abb53f2fb98 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 2 Nov 2026 16:48:04 +0100 Subject: [PATCH 5/5] fix: address s3-action review feedback - Replace invalid `simple:amazons3` icon (AWS icons were pulled from Simple Icons) with `tabler:bucket`. - Stop caching the S3 client: a single action process serves many flows with different matched configs, so build a fresh client per call. - Split the functions into two mirrored sets (per review): an object set (raw data at an explicit key) and a file set (hercules FILE values keyed by file name), each with upload/download/delete/list/presigned. Adds the S3_OBJECT_DATA data type for raw-text object bodies. - Update README and tests accordingly. Co-Authored-By: Claude Opus 4.8 --- actions/s3-action/README.md | 28 ++++-- .../s3-action/src/data_types/s3ObjectData.ts | 26 +++++ .../files/createPresignedUrlFunction.ts | 90 ++++++++++++++++++ .../src/functions/files/deleteFileFunction.ts | 61 ++++++++++++ .../downloadFileFunction.ts} | 20 ++-- .../src/functions/files/listFilesFunction.ts | 94 +++++++++++++++++++ .../uploadFilesFunction.ts} | 20 ++-- .../createPresignedUrlFunction.ts | 16 ++-- .../{ => objects}/deleteObjectFunction.ts | 8 +- .../objects/downloadObjectFunction.ts | 65 +++++++++++++ .../{ => objects}/listObjectsFunction.ts | 6 +- .../functions/objects/uploadObjectFunction.ts | 83 ++++++++++++++++ actions/s3-action/src/helpers.ts | 29 ++---- actions/s3-action/src/index.ts | 36 +++++-- actions/s3-action/test/index.test.ts | 14 +++ 15 files changed, 521 insertions(+), 75 deletions(-) create mode 100644 actions/s3-action/src/data_types/s3ObjectData.ts create mode 100644 actions/s3-action/src/functions/files/createPresignedUrlFunction.ts create mode 100644 actions/s3-action/src/functions/files/deleteFileFunction.ts rename actions/s3-action/src/functions/{getObjectFunction.ts => files/downloadFileFunction.ts} (72%) create mode 100644 actions/s3-action/src/functions/files/listFilesFunction.ts rename actions/s3-action/src/functions/{putObjectFunction.ts => files/uploadFilesFunction.ts} (78%) rename actions/s3-action/src/functions/{ => objects}/createPresignedUrlFunction.ts (84%) rename actions/s3-action/src/functions/{ => objects}/deleteObjectFunction.ts (84%) create mode 100644 actions/s3-action/src/functions/objects/downloadObjectFunction.ts rename actions/s3-action/src/functions/{ => objects}/listObjectsFunction.ts (94%) create mode 100644 actions/s3-action/src/functions/objects/uploadObjectFunction.ts diff --git a/actions/s3-action/README.md b/actions/s3-action/README.md index aca4beb..79cdc2a 100644 --- a/actions/s3-action/README.md +++ b/actions/s3-action/README.md @@ -7,18 +7,32 @@ DigitalOcean Spaces, ...) by pointing the `endpoint` config at that provider. ## Functions +The action exposes two parallel function sets. The **object** set is the +S3-native primitive: you give an explicit key and exchange the body as raw +(UTF-8 text) data. The **file** set works with hercules' standard `FILE` value +(base64 content plus a file name/content type), deriving object keys from the +file name — so a file downloaded from S3 can be piped directly into, say, an +HTTP upload, and vice versa. Pick the set that matches the data you have. + +### Object functions (raw data, explicit key) + | Function | Description | |----------|-------------| -| `putObject` | Upload one or more objects, creating or overwriting them. Each object's key is the file's own name, optionally under a `KeyPrefix`. | -| `getObject` | Download an object's content and metadata. | +| `uploadObject` | Upload raw text `Data` to an explicit `Key`, creating or overwriting the object. | +| `downloadObject` | Download an object's body as raw UTF-8 text, plus metadata. | | `deleteObject` | Delete an object. | | `listObjects` | List objects in a bucket, optionally filtered by prefix. | -| `createPresignedUrl` | Generate a temporary signed URL for direct GET or PUT access. | +| `createObjectPresignedUrl` | Generate a temporary signed URL for direct GET or PUT access. | + +### File functions (hercules `FILE` values) -Object content is exchanged as hercules' standard `FILE` value (base64 content -plus an optional file name/content type), the same type other actions/functions -in a flow produce and consume — so a file downloaded from S3 can be piped -directly into, say, an HTTP upload, and vice versa. +| Function | Description | +|----------|-------------| +| `uploadFiles` | Upload one or more `FILE` values, creating or overwriting them. Each file's key is its own name, optionally under a `KeyPrefix`. | +| `downloadFile` | Download an object's content as a `FILE` value (base64), plus metadata. | +| `deleteFile` | Delete a file. | +| `listFiles` | List files in a bucket, optionally filtered by prefix. | +| `createFilePresignedUrl` | Generate a temporary signed URL for direct GET or PUT access. | ## Configuration diff --git a/actions/s3-action/src/data_types/s3ObjectData.ts b/actions/s3-action/src/data_types/s3ObjectData.ts new file mode 100644 index 0000000..26fe4bd --- /dev/null +++ b/actions/s3-action/src/data_types/s3ObjectData.ts @@ -0,0 +1,26 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * The raw (textual) body of an S3 object, used by the "object" function set. + * Unlike {@link S3_OBJECT_CONTENT} (the "file" set), this does not wrap a std + * FILE value: `Data` is the object body decoded as UTF-8 text, so it can be + * fed straight into text-oriented functions. Binary payloads should use the + * file set instead, which exchanges content as base64 FILE values. + */ +export const S3ObjectDataSchema = z.object({ + Key: z.string(), + Bucket: z.string(), + Data: z.string(), + ContentType: z.string().optional(), + ContentLength: z.number().optional(), + ETag: z.string().optional(), + LastModified: z.string().optional(), +}); +export type S3ObjectData = z.infer; + +@Identifier("S3_OBJECT_DATA") +@Name({ code: "en-US", content: "S3 object data" }) +@DisplayMessage({ code: "en-US", content: "S3 object data ${Key}" }) +@Schema(S3ObjectDataSchema) +export class S3ObjectDataDataType {} diff --git a/actions/s3-action/src/functions/files/createPresignedUrlFunction.ts b/actions/s3-action/src/functions/files/createPresignedUrlFunction.ts new file mode 100644 index 0000000..5bac0f3 --- /dev/null +++ b/actions/s3-action/src/functions/files/createPresignedUrlFunction.ts @@ -0,0 +1,90 @@ +import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3PresignedUrl, S3PresignedUrlSchema } from "../../data_types/s3PresignedUrl.js"; + +@Identifier("createFilePresignedUrl") +@DisplayIcon("tabler:bucket") +@Signature('(Key: string, Operation?: "get"|"put", ExpiresInSeconds?: number, Bucket?: string): S3_PRESIGNED_URL') +@Name({ code: "en-US", content: "Create file presigned URL" }) +@DisplayMessage({ code: "en-US", content: "Create file presigned URL for ${Key}" }) +@Documentation({ + code: "en-US", + content: + "Generates a temporary, signed URL that grants direct GET or PUT access to a file without sharing the configured credentials. The URL expires after ExpiresInSeconds (default 900, i.e. 15 minutes).", +}) +@Description({ + code: "en-US", + content: "Generates a temporary presigned URL for a file.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to grant access to within the bucket." }], +}) +@Parameter({ + runtimeName: "Operation", + name: [{ code: "en-US", content: "Operation" }], + description: [{ code: "en-US", content: "Whether the URL grants read ('get') or write ('put') access. Defaults to 'get'." }], + optional: true, +}) +@Parameter({ + runtimeName: "ExpiresInSeconds", + name: [{ code: "en-US", content: "Expires in (seconds)" }], + description: [{ code: "en-US", content: "How long the URL stays valid, in seconds. Defaults to 900 (15 minutes)." }], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket the object lives in. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class CreateFilePresignedUrlFunction { + async run( + context: FunctionContext, + Key: string, + Operation?: "get" | "put", + ExpiresInSeconds?: number, + Bucket?: string + ): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + const operation = Operation ?? "get"; + const expiresIn = ExpiresInSeconds ?? 900; + + if (operation !== "get" && operation !== "put") { + throw new RuntimeError("S3_INVALID_PRESIGNED_URL_OPERATION", `Operation must be 'get' or 'put', got '${operation}'.`); + } + + try { + const command = + operation === "get" ? new GetObjectCommand({ Bucket: bucket, Key }) : new PutObjectCommand({ Bucket: bucket, Key }); + const url = await getSignedUrl(client, command, { expiresIn }); + + return S3PresignedUrlSchema.parse({ + Url: url, + Key, + Bucket: bucket, + Operation: operation, + ExpiresInSeconds: expiresIn, + }); + } catch (error) { + throw toRuntimeError("S3_CREATE_FILE_PRESIGNED_URL_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/files/deleteFileFunction.ts b/actions/s3-action/src/functions/files/deleteFileFunction.ts new file mode 100644 index 0000000..17706f7 --- /dev/null +++ b/actions/s3-action/src/functions/files/deleteFileFunction.ts @@ -0,0 +1,61 @@ +import { DeleteObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3DeleteResult, S3DeleteResultSchema } from "../../data_types/s3DeleteResult.js"; + +@Identifier("deleteFile") +@DisplayIcon("tabler:bucket") +@Signature("(Key: string, Bucket?: string): S3_DELETE_RESULT") +@Name({ code: "en-US", content: "Delete file" }) +@DisplayMessage({ code: "en-US", content: "Delete file ${Key}" }) +@Documentation({ + code: "en-US", + content: "Deletes a file from S3 by its key. Deleting a key that does not exist is not an error.", +}) +@Description({ + code: "en-US", + content: "Deletes a file from S3.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to delete within the bucket." }], +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to delete from. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class DeleteFileFunction { + async run(context: FunctionContext, Key: string, Bucket?: string): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send(new DeleteObjectCommand({ Bucket: bucket, Key })); + + return S3DeleteResultSchema.parse({ + Key, + Bucket: bucket, + Deleted: true, + DeleteMarker: result.DeleteMarker, + VersionId: result.VersionId, + }); + } catch (error) { + throw toRuntimeError("S3_DELETE_FILE_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/getObjectFunction.ts b/actions/s3-action/src/functions/files/downloadFileFunction.ts similarity index 72% rename from actions/s3-action/src/functions/getObjectFunction.ts rename to actions/s3-action/src/functions/files/downloadFileFunction.ts index 2c531ab..7aef070 100644 --- a/actions/s3-action/src/functions/getObjectFunction.ts +++ b/actions/s3-action/src/functions/files/downloadFileFunction.ts @@ -10,22 +10,22 @@ import { Parameter, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; -import { S3ObjectContent, S3ObjectContentSchema } from "../data_types/s3ObjectContent.js"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3ObjectContent, S3ObjectContentSchema } from "../../data_types/s3ObjectContent.js"; -@Identifier("getObject") -@DisplayIcon("simple:amazons3") +@Identifier("downloadFile") +@DisplayIcon("tabler:bucket") @Signature("(Key: string, Bucket?: string): S3_OBJECT_CONTENT") -@Name({ code: "en-US", content: "Download object" }) -@DisplayMessage({ code: "en-US", content: "Download object ${Key}" }) +@Name({ code: "en-US", content: "Download file" }) +@DisplayMessage({ code: "en-US", content: "Download file ${Key}" }) @Documentation({ code: "en-US", content: - "Downloads an object from S3 and returns its content as a standard FILE value (base64-encoded content plus content type), so it can be passed directly to other functions/actions that accept a FILE.", + "Downloads an object from S3 and returns its content as a standard hercules FILE value (base64-encoded content plus content type), so it can be passed directly to other functions/actions that accept a FILE. To receive the body as raw text, use `Download object` instead.", }) @Description({ code: "en-US", - content: "Downloads an object from S3.", + content: "Downloads a file from S3.", }) @Parameter({ runtimeName: "Key", @@ -40,7 +40,7 @@ import { S3ObjectContent, S3ObjectContentSchema } from "../data_types/s3ObjectCo ], optional: true, }) -export class GetObjectFunction { +export class DownloadFileFunction { async run(context: FunctionContext, Key: string, Bucket?: string): Promise { const bucket = resolveBucket(context, Bucket); const client = getS3Client(context); @@ -63,7 +63,7 @@ export class GetObjectFunction { LastModified: result.LastModified?.toISOString(), }); } catch (error) { - throw toRuntimeError("S3_GET_OBJECT_FAILED", error); + throw toRuntimeError("S3_DOWNLOAD_FILE_FAILED", error); } } } diff --git a/actions/s3-action/src/functions/files/listFilesFunction.ts b/actions/s3-action/src/functions/files/listFilesFunction.ts new file mode 100644 index 0000000..3d17703 --- /dev/null +++ b/actions/s3-action/src/functions/files/listFilesFunction.ts @@ -0,0 +1,94 @@ +import { ListObjectsV2Command } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3ObjectList, S3ObjectListSchema } from "../../data_types/s3ObjectList.js"; + +@Identifier("listFiles") +@DisplayIcon("tabler:bucket") +@Signature("(Prefix?: string, Bucket?: string, MaxKeys?: number, ContinuationToken?: string): S3_OBJECT_LIST") +@Name({ code: "en-US", content: "List files" }) +@DisplayMessage({ code: "en-US", content: "List files in ${Bucket}" }) +@Documentation({ + code: "en-US", + content: + "Lists files in a bucket, optionally filtered by key prefix. Results are paginated; pass the returned NextContinuationToken back in as ContinuationToken to fetch the next page.", +}) +@Description({ + code: "en-US", + content: "Lists files in a bucket.", +}) +@Parameter({ + runtimeName: "Prefix", + name: [{ code: "en-US", content: "Prefix" }], + description: [{ code: "en-US", content: "Only return files whose key starts with this prefix." }], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to list. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +@Parameter({ + runtimeName: "MaxKeys", + name: [{ code: "en-US", content: "Max keys" }], + description: [{ code: "en-US", content: "The maximum number of keys to return in one page. Defaults to 1000." }], + optional: true, +}) +@Parameter({ + runtimeName: "ContinuationToken", + name: [{ code: "en-US", content: "Continuation token" }], + description: [{ code: "en-US", content: "Token from a previous call's NextContinuationToken, to fetch the next page." }], + optional: true, +}) +export class ListFilesFunction { + async run( + context: FunctionContext, + Prefix?: string, + Bucket?: string, + MaxKeys?: number, + ContinuationToken?: string + ): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + ...(Prefix ? { Prefix } : {}), + ...(MaxKeys ? { MaxKeys } : {}), + ...(ContinuationToken ? { ContinuationToken } : {}), + }) + ); + + return S3ObjectListSchema.parse({ + Bucket: bucket, + Prefix, + Objects: (result.Contents ?? []).map((object) => ({ + Key: object.Key, + Size: object.Size, + ETag: object.ETag, + LastModified: object.LastModified?.toISOString(), + StorageClass: object.StorageClass, + })), + IsTruncated: result.IsTruncated, + NextContinuationToken: result.NextContinuationToken, + }); + } catch (error) { + throw toRuntimeError("S3_LIST_FILES_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/putObjectFunction.ts b/actions/s3-action/src/functions/files/uploadFilesFunction.ts similarity index 78% rename from actions/s3-action/src/functions/putObjectFunction.ts rename to actions/s3-action/src/functions/files/uploadFilesFunction.ts index 1e85ada..f938490 100644 --- a/actions/s3-action/src/functions/putObjectFunction.ts +++ b/actions/s3-action/src/functions/files/uploadFilesFunction.ts @@ -11,22 +11,22 @@ import { Parameter, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, resolveObjectKey, toRuntimeError } from "../helpers.js"; -import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; +import { getS3Client, resolveBucket, resolveObjectKey, toRuntimeError } from "../../helpers.js"; +import { S3Object, S3ObjectSchema } from "../../data_types/s3Object.js"; -@Identifier("putObject") -@DisplayIcon("simple:amazons3") +@Identifier("uploadFiles") +@DisplayIcon("tabler:bucket") @Signature('(Files: FILE<"*/*">[], KeyPrefix?: string, Bucket?: string): S3_OBJECT[]') -@Name({ code: "en-US", content: "Upload objects" }) -@DisplayMessage({ code: "en-US", content: "Upload objects under ${KeyPrefix}" }) +@Name({ code: "en-US", content: "Upload files" }) +@DisplayMessage({ code: "en-US", content: "Upload files under ${KeyPrefix}" }) @Documentation({ code: "en-US", content: - "Uploads one or more objects to S3 (or an S3-compatible provider), creating them or overwriting existing objects with the same key. Each object's key is the uploaded file's own name, optionally placed under `KeyPrefix` (e.g. `reports/`). `Files` are standard FILE values (base64-encoded content plus a file name and optional content type).", + "Uploads one or more files (standard hercules FILE values: base64 content plus a file name and optional content type) to S3, creating them or overwriting existing objects with the same key. Each file's key is its own name, optionally placed under `KeyPrefix` (e.g. `reports/`). To upload raw data at an explicit key, use `Upload object` instead.", }) @Description({ code: "en-US", - content: "Uploads one or more objects to S3.", + content: "Uploads one or more files to S3.", }) @Parameter({ runtimeName: "Files", @@ -58,7 +58,7 @@ import { S3Object, S3ObjectSchema } from "../data_types/s3Object.js"; ], optional: true, }) -export class PutObjectFunction { +export class UploadFilesFunction { async run(context: FunctionContext, Files: File<"*/*">[], KeyPrefix?: string, Bucket?: string): Promise { const bucket = resolveBucket(context, Bucket); const client = getS3Client(context); @@ -92,7 +92,7 @@ export class PutObjectFunction { }) ); } catch (error) { - throw toRuntimeError("S3_PUT_OBJECT_FAILED", error); + throw toRuntimeError("S3_UPLOAD_FILES_FAILED", error); } } } diff --git a/actions/s3-action/src/functions/createPresignedUrlFunction.ts b/actions/s3-action/src/functions/objects/createPresignedUrlFunction.ts similarity index 84% rename from actions/s3-action/src/functions/createPresignedUrlFunction.ts rename to actions/s3-action/src/functions/objects/createPresignedUrlFunction.ts index b42c759..0982bc9 100644 --- a/actions/s3-action/src/functions/createPresignedUrlFunction.ts +++ b/actions/s3-action/src/functions/objects/createPresignedUrlFunction.ts @@ -12,14 +12,14 @@ import { RuntimeError, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; -import { S3PresignedUrl, S3PresignedUrlSchema } from "../data_types/s3PresignedUrl.js"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3PresignedUrl, S3PresignedUrlSchema } from "../../data_types/s3PresignedUrl.js"; -@Identifier("createPresignedUrl") -@DisplayIcon("simple:amazons3") +@Identifier("createObjectPresignedUrl") +@DisplayIcon("tabler:bucket") @Signature('(Key: string, Operation?: "get"|"put", ExpiresInSeconds?: number, Bucket?: string): S3_PRESIGNED_URL') -@Name({ code: "en-US", content: "Create presigned URL" }) -@DisplayMessage({ code: "en-US", content: "Create presigned URL for ${Key}" }) +@Name({ code: "en-US", content: "Create object presigned URL" }) +@DisplayMessage({ code: "en-US", content: "Create object presigned URL for ${Key}" }) @Documentation({ code: "en-US", content: @@ -54,7 +54,7 @@ import { S3PresignedUrl, S3PresignedUrlSchema } from "../data_types/s3PresignedU ], optional: true, }) -export class CreatePresignedUrlFunction { +export class CreateObjectPresignedUrlFunction { async run( context: FunctionContext, Key: string, @@ -84,7 +84,7 @@ export class CreatePresignedUrlFunction { ExpiresInSeconds: expiresIn, }); } catch (error) { - throw toRuntimeError("S3_CREATE_PRESIGNED_URL_FAILED", error); + throw toRuntimeError("S3_CREATE_OBJECT_PRESIGNED_URL_FAILED", error); } } } diff --git a/actions/s3-action/src/functions/deleteObjectFunction.ts b/actions/s3-action/src/functions/objects/deleteObjectFunction.ts similarity index 84% rename from actions/s3-action/src/functions/deleteObjectFunction.ts rename to actions/s3-action/src/functions/objects/deleteObjectFunction.ts index 35e09f5..4a74d12 100644 --- a/actions/s3-action/src/functions/deleteObjectFunction.ts +++ b/actions/s3-action/src/functions/objects/deleteObjectFunction.ts @@ -10,17 +10,17 @@ import { Parameter, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; -import { S3DeleteResult, S3DeleteResultSchema } from "../data_types/s3DeleteResult.js"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3DeleteResult, S3DeleteResultSchema } from "../../data_types/s3DeleteResult.js"; @Identifier("deleteObject") -@DisplayIcon("simple:amazons3") +@DisplayIcon("tabler:bucket") @Signature("(Key: string, Bucket?: string): S3_DELETE_RESULT") @Name({ code: "en-US", content: "Delete object" }) @DisplayMessage({ code: "en-US", content: "Delete object ${Key}" }) @Documentation({ code: "en-US", - content: "Deletes an object from S3. Deleting a key that does not exist is not an error.", + content: "Deletes an object from S3 by its key. Deleting a key that does not exist is not an error.", }) @Description({ code: "en-US", diff --git a/actions/s3-action/src/functions/objects/downloadObjectFunction.ts b/actions/s3-action/src/functions/objects/downloadObjectFunction.ts new file mode 100644 index 0000000..f053440 --- /dev/null +++ b/actions/s3-action/src/functions/objects/downloadObjectFunction.ts @@ -0,0 +1,65 @@ +import { GetObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3ObjectData, S3ObjectDataSchema } from "../../data_types/s3ObjectData.js"; + +@Identifier("downloadObject") +@DisplayIcon("tabler:bucket") +@Signature("(Key: string, Bucket?: string): S3_OBJECT_DATA") +@Name({ code: "en-US", content: "Download object" }) +@DisplayMessage({ code: "en-US", content: "Download object ${Key}" }) +@Documentation({ + code: "en-US", + content: + "Downloads an object from S3 and returns its body as raw UTF-8 text. This is the object-storage primitive; to receive the content as a hercules FILE value (base64, for binary content or chaining into FILE-consuming functions), use `Download file` instead.", +}) +@Description({ + code: "en-US", + content: "Downloads an object from S3 as raw data.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to read within the bucket." }], +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to read from. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class DownloadObjectFunction { + async run(context: FunctionContext, Key: string, Bucket?: string): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + + try { + const result = await client.send(new GetObjectCommand({ Bucket: bucket, Key })); + const data = (await result.Body?.transformToString("utf-8")) ?? ""; + + return S3ObjectDataSchema.parse({ + Key, + Bucket: bucket, + Data: data, + ContentType: result.ContentType, + ContentLength: result.ContentLength, + ETag: result.ETag, + LastModified: result.LastModified?.toISOString(), + }); + } catch (error) { + throw toRuntimeError("S3_DOWNLOAD_OBJECT_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/functions/listObjectsFunction.ts b/actions/s3-action/src/functions/objects/listObjectsFunction.ts similarity index 94% rename from actions/s3-action/src/functions/listObjectsFunction.ts rename to actions/s3-action/src/functions/objects/listObjectsFunction.ts index df1e25d..aff6053 100644 --- a/actions/s3-action/src/functions/listObjectsFunction.ts +++ b/actions/s3-action/src/functions/objects/listObjectsFunction.ts @@ -10,11 +10,11 @@ import { Parameter, Signature, } from "@code0-tech/hercules"; -import { getS3Client, resolveBucket, toRuntimeError } from "../helpers.js"; -import { S3ObjectList, S3ObjectListSchema } from "../data_types/s3ObjectList.js"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3ObjectList, S3ObjectListSchema } from "../../data_types/s3ObjectList.js"; @Identifier("listObjects") -@DisplayIcon("simple:amazons3") +@DisplayIcon("tabler:bucket") @Signature("(Prefix?: string, Bucket?: string, MaxKeys?: number, ContinuationToken?: string): S3_OBJECT_LIST") @Name({ code: "en-US", content: "List objects" }) @DisplayMessage({ code: "en-US", content: "List objects in ${Bucket}" }) diff --git a/actions/s3-action/src/functions/objects/uploadObjectFunction.ts b/actions/s3-action/src/functions/objects/uploadObjectFunction.ts new file mode 100644 index 0000000..3e7004a --- /dev/null +++ b/actions/s3-action/src/functions/objects/uploadObjectFunction.ts @@ -0,0 +1,83 @@ +import { PutObjectCommand } from "@aws-sdk/client-s3"; +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getS3Client, resolveBucket, toRuntimeError } from "../../helpers.js"; +import { S3Object, S3ObjectSchema } from "../../data_types/s3Object.js"; + +@Identifier("uploadObject") +@DisplayIcon("tabler:bucket") +@Signature("(Key: string, Data: string, ContentType?: string, Bucket?: string): S3_OBJECT") +@Name({ code: "en-US", content: "Upload object" }) +@DisplayMessage({ code: "en-US", content: "Upload object ${Key}" }) +@Documentation({ + code: "en-US", + content: + "Uploads a single object to S3 (or an S3-compatible provider) at the exact `Key` given, from raw textual `Data`. This is the object-storage primitive: the key is explicit and the body is a UTF-8 string. To upload binary content or hercules FILE values, use `Upload files` instead.", +}) +@Description({ + code: "en-US", + content: "Uploads raw data to S3 at an explicit key.", +}) +@Parameter({ + runtimeName: "Key", + name: [{ code: "en-US", content: "Key" }], + description: [{ code: "en-US", content: "The object key (path) to write within the bucket." }], +}) +@Parameter({ + runtimeName: "Data", + name: [{ code: "en-US", content: "Data" }], + description: [{ code: "en-US", content: "The object body, as UTF-8 text." }], +}) +@Parameter({ + runtimeName: "ContentType", + name: [{ code: "en-US", content: "Content type" }], + description: [ + { code: "en-US", content: "Optional MIME type stored with the object. Defaults to 'text/plain; charset=utf-8'." }, + ], + optional: true, +}) +@Parameter({ + runtimeName: "Bucket", + name: [{ code: "en-US", content: "Bucket" }], + description: [ + { code: "en-US", content: "The bucket to upload to. Falls back to the configured default bucket when omitted." }, + ], + optional: true, +}) +export class UploadObjectFunction { + async run(context: FunctionContext, Key: string, Data: string, ContentType?: string, Bucket?: string): Promise { + const bucket = resolveBucket(context, Bucket); + const client = getS3Client(context); + const contentType = ContentType || "text/plain; charset=utf-8"; + + try { + const result = await client.send( + new PutObjectCommand({ + Bucket: bucket, + Key, + Body: Buffer.from(Data ?? "", "utf-8"), + ContentType: contentType, + }) + ); + + return S3ObjectSchema.parse({ + Key, + Bucket: bucket, + ETag: result.ETag, + Location: `${bucket}/${Key}`, + VersionId: result.VersionId, + }); + } catch (error) { + throw toRuntimeError("S3_UPLOAD_OBJECT_FAILED", error); + } + } +} diff --git a/actions/s3-action/src/helpers.ts b/actions/s3-action/src/helpers.ts index 6e20de3..b4d54dc 100644 --- a/actions/s3-action/src/helpers.ts +++ b/actions/s3-action/src/helpers.ts @@ -2,20 +2,15 @@ import { S3Client } from "@aws-sdk/client-s3"; import { FunctionContext, RuntimeError } from "@code0-tech/hercules"; /** - * Builds (and caches) an S3 client from the action configuration. + * Builds an S3 client from the action configuration. * - * The client is cached per (accessKeyId, secretAccessKey, region, endpoint) - * combination so repeated function invocations reuse the same instance and - * connection pool, while a credential or region change (e.g. key rotation) - * still produces a fresh client instead of silently reusing a stale one. + * A fresh client is built per call: a single action process serves many flows, + * each with its own matched config (different credentials, region or endpoint), + * so there is no single client that can be safely reused across invocations. * Uses the official `@aws-sdk/client-s3`, which works against AWS S3 and any * S3-compatible provider (MinIO, Cloudflare R2, DigitalOcean Spaces, ...) by * pointing `endpoint` at that provider. - */ -let cached: { accessKeyId: string; secretAccessKey: string; region: string; endpoint: string; client: S3Client } | null = - null; - -/** + * * access_key_id / secret_access_key are optional: when either is left * unconfigured, the AWS SDK's default credential provider chain is used * instead (environment variables, shared config/credentials file, or an @@ -28,17 +23,7 @@ export function getS3Client(context: FunctionContext): S3Client { const region = (context.matchedConfig.findConfig("region") as string) || "us-east-1"; const endpoint = (context.matchedConfig.findConfig("endpoint") as string) || ""; - if ( - cached && - cached.accessKeyId === accessKeyId && - cached.secretAccessKey === secretAccessKey && - cached.region === region && - cached.endpoint === endpoint - ) { - return cached.client; - } - - const client = new S3Client({ + return new S3Client({ region, ...(accessKeyId && secretAccessKey ? { credentials: { accessKeyId, secretAccessKey } } : {}), // A custom endpoint is only set for S3-compatible providers (MinIO, @@ -46,8 +31,6 @@ export function getS3Client(context: FunctionContext): S3Client { // addressing. Real AWS S3 keeps using virtual-hosted-style requests. ...(endpoint ? { endpoint, forcePathStyle: true } : {}), }); - cached = { accessKeyId, secretAccessKey, region, endpoint, client }; - return client; } /** diff --git a/actions/s3-action/src/index.ts b/actions/s3-action/src/index.ts index 19a6ce0..bbe2900 100644 --- a/actions/s3-action/src/index.ts +++ b/actions/s3-action/src/index.ts @@ -3,23 +3,32 @@ import { Action, CodeZeroEvent } from "@code0-tech/hercules"; import { S3ObjectDataType } from "./data_types/s3Object.js"; import { S3ObjectContentDataType } from "./data_types/s3ObjectContent.js"; +import { S3ObjectDataDataType } from "./data_types/s3ObjectData.js"; import { S3DeleteResultDataType } from "./data_types/s3DeleteResult.js"; import { S3ObjectListDataType } from "./data_types/s3ObjectList.js"; import { S3PresignedUrlDataType } from "./data_types/s3PresignedUrl.js"; -import { PutObjectFunction } from "./functions/putObjectFunction.js"; -import { GetObjectFunction } from "./functions/getObjectFunction.js"; -import { DeleteObjectFunction } from "./functions/deleteObjectFunction.js"; -import { ListObjectsFunction } from "./functions/listObjectsFunction.js"; -import { CreatePresignedUrlFunction } from "./functions/createPresignedUrlFunction.js"; +// Object function set: S3-native primitives (explicit key + raw data). +import { UploadObjectFunction } from "./functions/objects/uploadObjectFunction.js"; +import { DownloadObjectFunction } from "./functions/objects/downloadObjectFunction.js"; +import { DeleteObjectFunction } from "./functions/objects/deleteObjectFunction.js"; +import { ListObjectsFunction } from "./functions/objects/listObjectsFunction.js"; +import { CreateObjectPresignedUrlFunction } from "./functions/objects/createPresignedUrlFunction.js"; + +// File function set: hercules FILE values (keys derived from file names). +import { UploadFilesFunction } from "./functions/files/uploadFilesFunction.js"; +import { DownloadFileFunction } from "./functions/files/downloadFileFunction.js"; +import { DeleteFileFunction } from "./functions/files/deleteFileFunction.js"; +import { ListFilesFunction } from "./functions/files/listFilesFunction.js"; +import { CreateFilePresignedUrlFunction } from "./functions/files/createPresignedUrlFunction.js"; const action = new Action( process.env.ACTION_ID ?? "s3-action", process.env.VERSION ?? "1.0.0", process.env.AQUILA_URL ?? "127.0.0.1:8081", "CodeZero", - "simple:amazons3", - "S3 / object storage integration: upload, download, list, and delete objects, and generate presigned URLs, against AWS S3 or any S3-compatible provider.", + "tabler:bucket", + "S3 / object storage integration against AWS S3 or any S3-compatible provider. Offers a parallel object set (raw data at an explicit key) and file set (hercules FILE values keyed by file name): upload, download, list, delete, and presigned URLs.", [{ code: "en-US", content: "S3" }], [ { @@ -92,15 +101,22 @@ const action = new Action( action.registerDataTypeClass(S3ObjectDataType); action.registerDataTypeClass(S3ObjectContentDataType); +action.registerDataTypeClass(S3ObjectDataDataType); action.registerDataTypeClass(S3DeleteResultDataType); action.registerDataTypeClass(S3ObjectListDataType); action.registerDataTypeClass(S3PresignedUrlDataType); -action.registerRuntimeFunction(PutObjectFunction); -action.registerRuntimeFunction(GetObjectFunction); +action.registerRuntimeFunction(UploadObjectFunction); +action.registerRuntimeFunction(DownloadObjectFunction); action.registerRuntimeFunction(DeleteObjectFunction); action.registerRuntimeFunction(ListObjectsFunction); -action.registerRuntimeFunction(CreatePresignedUrlFunction); +action.registerRuntimeFunction(CreateObjectPresignedUrlFunction); + +action.registerRuntimeFunction(UploadFilesFunction); +action.registerRuntimeFunction(DownloadFileFunction); +action.registerRuntimeFunction(DeleteFileFunction); +action.registerRuntimeFunction(ListFilesFunction); +action.registerRuntimeFunction(CreateFilePresignedUrlFunction); action.on(CodeZeroEvent.connected, () => { console.log("Connected to aquila"); diff --git a/actions/s3-action/test/index.test.ts b/actions/s3-action/test/index.test.ts index 27a7b8a..c6c26de 100644 --- a/actions/s3-action/test/index.test.ts +++ b/actions/s3-action/test/index.test.ts @@ -4,6 +4,7 @@ import type { FunctionContext } from "@code0-tech/hercules"; import { RuntimeError } from "@code0-tech/hercules"; import { S3ObjectSchema } from "../src/data_types/s3Object.js"; import { S3ObjectContentSchema } from "../src/data_types/s3ObjectContent.js"; +import { S3ObjectDataSchema } from "../src/data_types/s3ObjectData.js"; import { S3DeleteResultSchema } from "../src/data_types/s3DeleteResult.js"; import { S3ObjectListSchema } from "../src/data_types/s3ObjectList.js"; import { S3PresignedUrlSchema } from "../src/data_types/s3PresignedUrl.js"; @@ -51,6 +52,19 @@ describe("S3ObjectContentSchema", () => { }); }); +describe("S3ObjectDataSchema", () => { + it("parses a representative get-object result with a raw text body", () => { + const parsed = S3ObjectDataSchema.parse({ + Key: "reports/2026-11-01.json", + Bucket: "my-bucket", + Data: '{"status":"ok"}', + ContentType: "application/json", + ContentLength: 16, + }); + expect(parsed.Data).toEqual('{"status":"ok"}'); + }); +}); + describe("S3DeleteResultSchema", () => { it("parses a representative delete result", () => { const parsed = S3DeleteResultSchema.parse({ Key: "obsolete.txt", Bucket: "my-bucket", Deleted: true });